From 938f8c6b5126ce4b9a1ecbccb44e3bd087e9f5e0 Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Sun, 1 Dec 2024 00:42:00 +0300 Subject: [PATCH] update scene debug --- .../view/device_managment_page.dart | 2 +- .../bloc/routine_bloc/routine_bloc.dart | 383 +++++++++++------- .../bloc/routine_bloc/routine_event.dart | 7 + .../bloc/routine_bloc/routine_state.dart | 8 +- .../routiens/helper/save_routine_helper.dart | 2 - .../models/routine_details_model.dart | 17 +- .../view/create_new_routine_view.dart | 2 +- lib/pages/routiens/view/routines_view.dart | 4 +- .../routine_dialogs/automation_dialog.dart | 145 +++---- .../widgets/routine_search_and_buttons.dart | 135 +++--- .../routiens/widgets/then_container.dart | 99 +++-- lib/services/routines_api.dart | 1 - 12 files changed, 504 insertions(+), 301 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 06adfd6c..6fb41713 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 @@ -94,7 +94,7 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout { return const RoutinesView(); } if (state is ShowCreateRoutineState && state.showCreateRoutine) { - return const CreateNewRoutineView(); + return CreateNewRoutineView(); } return BlocBuilder( diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart index 13d9209c..e6c45594 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_bloc.dart @@ -13,6 +13,7 @@ import 'package:syncrow_web/pages/routiens/models/routine_model.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/navigation_service.dart'; import 'package:uuid/uuid.dart'; part 'routine_event.dart'; @@ -44,14 +45,15 @@ class RoutineBloc extends Bloc { on(_fetchDevices); on(_onUpdateScene); on(_onUpdateAutomation); + on(_onSetAutomationActionExecutor); } void _onAddToIfContainer(AddToIfContainer event, Emitter emit) { 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; @@ -60,18 +62,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; @@ -82,22 +87,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); @@ -107,13 +116,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)); @@ -122,11 +133,13 @@ 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)); try { - final scenes = await SceneApi.getScenesByUnitId(event.unitId, event.communityId); + final scenes = + await SceneApi.getScenesByUnitId(event.unitId, event.communityId); emit(state.copyWith( scenes: scenes, isLoading: false, @@ -141,7 +154,8 @@ 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)); try { @@ -169,14 +183,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)); } @@ -190,7 +206,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)) { @@ -257,7 +274,8 @@ class RoutineBloc extends Bloc { final result = await SceneApi.createScene(createSceneModel); if (result['success']) { - emit(_resetState()); + Navigator.of(NavigationService.navigatorKey.currentContext!).pop(); + add(ResetRoutineState()); add(const LoadScenes(spaceId, communityId)); add(const LoadAutomation(spaceId)); } else { @@ -274,7 +292,8 @@ 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( @@ -282,7 +301,21 @@ class RoutineBloc extends Bloc { )); return; } + if (_isFirstActionDelay(state.thenItems)) { + emit(state.copyWith( + errorMessage: 'Cannot have delay as the first action', + isLoading: false, + )); + return; + } + if (_isLastActionDelay(state.thenItems)) { + emit(state.copyWith( + errorMessage: 'Cannot have delay as the last action', + isLoading: false, + )); + return; + } emit(state.copyWith(isLoading: true)); final conditions = state.ifItems.expand((item) { @@ -358,7 +391,8 @@ class RoutineBloc extends Bloc { final result = await SceneApi.createAutomation(createAutomationModel); if (result['success']) { - emit(_resetState()); + Navigator.of(NavigationService.navigatorKey.currentContext!).pop(); + add(ResetRoutineState()); add(const LoadAutomation(spaceId)); add(const LoadScenes(spaceId, communityId)); } else { @@ -375,17 +409,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); @@ -396,7 +434,8 @@ class RoutineBloc extends Bloc { isAutomation: false, isTabToRun: false)); } else { - emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions)); + emit(state.copyWith( + ifItems: ifItems, selectedFunctions: selectedFunctions)); } } } @@ -408,16 +447,21 @@ 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, @@ -450,7 +494,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; @@ -486,8 +531,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), }; @@ -530,16 +578,26 @@ 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, - isTabToRun: event.isTabToRun, + isTabToRun: true, isUpdate: true, sceneId: event.sceneId, + routineName: null, isAutomation: false, + selectedFunctions: {}, ifItems: [], thenItems: [], + errorMessage: null, + loadScenesErrorMessage: null, + loadAutomationErrorMessage: null, + searchText: '', + selectedIcon: null, + selectedAutomationOperator: 'or', + effectiveTime: null, )); final sceneDetails = await SceneApi.getSceneDetails(event.sceneId); @@ -549,20 +607,32 @@ class RoutineBloc extends Bloc { final Map> updatedFunctions = Map>.from(state.selectedFunctions); - if (event.isTabToRun) { - thenItems = sceneDetails.actions.map((action) { - AllDevicesModel? matchingDevice; - for (var device in state.devices) { - if (device.uuid == action.entityId) { - matchingDevice = device; - break; - } - } + final Map> deviceCards = {}; - final cardData = { + for (var action in sceneDetails.actions) { + AllDevicesModel? matchingDevice; + for (var device in state.devices) { + if (device.uuid == action.entityId) { + matchingDevice = device; + break; + } + } + + final deviceId = action.type == 'automation' + ? '${action.entityId}_automation' + : action.actionExecutor == 'delay' + ? '${action.entityId}_delay' + : action.entityId; + + if (!deviceCards.containsKey(deviceId)) { + deviceCards[deviceId] = { 'entityId': action.entityId, - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': action.actionExecutor == 'delay' ? 'delay' : 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' @@ -570,75 +640,85 @@ class RoutineBloc extends Bloc { : (matchingDevice?.name ?? 'Device'), 'productType': action.productType, 'functions': matchingDevice?.functions, - 'imagePath': matchingDevice?.getDefaultIcon(action.productType), - 'device': matchingDevice ?? null, + 'imagePath': action.type == 'automation' + ? Assets.automation + : action.actionExecutor == 'delay' + ? Assets.delay + : matchingDevice?.getDefaultIcon(action.productType), + 'device': matchingDevice, 'name': action.name, 'type': action.type, }; - if (action.type == 'automation') { - updatedFunctions[cardData['uniqueCustomId'].toString()] = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: 'rule', - value: action.actionExecutor, - operationName: action.name ?? 'Automation', - ), - ]; - } else if (action.executorProperty != null && action.actionExecutor != 'delay') { - final functions = matchingDevice?.functions; - final functionCode = action.executorProperty!.functionCode; - for (var function in functions ?? []) { - if (function.code == functionCode) { - updatedFunctions[cardData['uniqueCustomId'].toString()] = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: functionCode ?? '', - value: action.executorProperty!.functionValue, - operationName: function.operationName, - ), - ]; - break; - } + } + + final cardData = deviceCards[deviceId]!; + final uniqueCustomId = cardData['uniqueCustomId'].toString(); + + if (action.type == 'automation') { + if (!updatedFunctions.containsKey(uniqueCustomId)) { + updatedFunctions[uniqueCustomId] = []; + } + updatedFunctions[uniqueCustomId]!.add( + DeviceFunctionData( + entityId: action.entityId, + functionCode: 'automation', + value: action.actionExecutor, + operationName: 'Automation', + ), + ); + 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 functionCode = action.executorProperty?.functionCode; + for (DeviceFunction function in functions ?? []) { + if (function.code == functionCode) { + updatedFunctions[uniqueCustomId]!.add( + 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', - ); - updatedFunctions[cardData['uniqueCustomId'].toString()] = [ - DeviceFunctionData( - entityId: action.entityId, - functionCode: 'delay', - value: action.executorProperty?.delaySeconds ?? 0, - operationName: delayFunction.operationName, - ), - ]; } - - return cardData; - }).toList(); - - ifItems = [ - { - 'entityId': 'tab_to_run', - 'uniqueCustomId': const Uuid().v4(), - 'deviceId': 'tab_to_run', - 'title': 'Tab to run', - 'productType': 'tab_to_run', - 'imagePath': Assets.tabToRun, + } else if (action.actionExecutor == 'delay') { + if (!updatedFunctions.containsKey(uniqueCustomId)) { + updatedFunctions[uniqueCustomId] = []; } - ]; - } else { - final result = _createCardData( - sceneDetails.actions, - sceneDetails.conditions, - updatedFunctions, - false, - ); - thenItems = result.$1; - ifItems = result.$2; + final delayFunction = DelayFunction( + deviceId: action.entityId, + deviceName: 'Delay', + ); + updatedFunctions[uniqueCustomId]!.add( + DeviceFunctionData( + entityId: action.entityId, + functionCode: 'delay', + value: action.executorProperty?.delaySeconds ?? 0, + operationName: delayFunction.operationName, + ), + ); + } } + thenItems = deviceCards.values.toList(); + + ifItems = [ + { + 'entityId': 'tab_to_run', + 'uniqueCustomId': const Uuid().v4(), + 'deviceId': 'tab_to_run', + 'title': 'Tab to run', + 'productType': 'tab_to_run', + 'imagePath': Assets.tabToRun, + } + ]; + emit(state.copyWith( isLoading: false, routineName: sceneDetails.name, @@ -673,7 +753,8 @@ class RoutineBloc extends Bloc { thenItems: [], )); - final automationDetails = await SceneApi.getAutomationDetails(event.automationId); + final automationDetails = + await SceneApi.getAutomationDetails(event.automationId); final List> thenItems; final List> ifItems; @@ -692,13 +773,16 @@ class RoutineBloc extends Bloc { final cardData = { 'entityId': condition.entityId, 'uniqueCustomId': const Uuid().v4(), - 'deviceId': condition.expr.statusCode == 'delay' ? 'delay' : condition.entityId, + 'deviceId': condition.expr.statusCode == 'delay' + ? 'delay' + : condition.entityId, 'title': condition.expr.statusCode == 'delay' ? 'Delay' : (matchingDevice?.name ?? 'Device'), 'productType': condition.productType, 'functions': matchingDevice?.functions ?? [], - 'imagePath': matchingDevice?.getDefaultIcon(condition.productType) ?? '', + 'imagePath': + matchingDevice?.getDefaultIcon(condition.productType) ?? '', 'device': matchingDevice, }; @@ -735,15 +819,19 @@ 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, 'functions': matchingDevice.functions, 'imagePath': matchingDevice.getDefaultIcon(action.productType), 'device': matchingDevice, }; - 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) { @@ -806,8 +894,9 @@ class RoutineBloc extends Bloc { } } - RoutineState _resetState() { - return const RoutineState( + FutureOr _onResetRoutineState( + ResetRoutineState event, Emitter emit) { + emit(state.copyWith( ifItems: [], thenItems: [], selectedFunctions: {}, @@ -824,11 +913,7 @@ class RoutineBloc extends Bloc { selectedAutomationOperator: 'or', effectiveTime: null, routineName: null, - ); - } - - FutureOr _onResetRoutineState(ResetRoutineState event, Emitter emit) { - emit(_resetState()); + )); } FutureOr _deleteScene(DeleteScene event, Emitter emit) { @@ -842,7 +927,7 @@ class RoutineBloc extends Bloc { add(const LoadScenes(spaceId, communityId)); add(const LoadAutomation(spaceId)); - emit(_resetState()); + add(ResetRoutineState()); } catch (e) { emit(state.copyWith( isLoading: false, @@ -865,7 +950,8 @@ 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 devices = await DevicesManagementApi().fetchDevices(); @@ -876,7 +962,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)) { @@ -884,6 +971,15 @@ class RoutineBloc extends Bloc { errorMessage: 'Cannot have delay as the first action', isLoading: false, )); + + return; + } + + if (_isLastActionDelay(state.thenItems)) { + emit(state.copyWith( + errorMessage: 'Cannot have delay as the last action', + isLoading: false, + )); return; } @@ -933,10 +1029,13 @@ 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']) { - emit(_resetState()); + Navigator.of(NavigationService.navigatorKey.currentContext!).pop(); + add(ResetRoutineState()); add(const LoadScenes(spaceId, communityId)); + add(const LoadAutomation(spaceId)); } else { emit(state.copyWith( isLoading: false, @@ -951,7 +1050,8 @@ class RoutineBloc extends Bloc { } } - FutureOr _onUpdateAutomation(UpdateAutomation event, Emitter emit) async { + FutureOr _onUpdateAutomation( + UpdateAutomation event, Emitter emit) async { if (_isFirstActionDelay(state.thenItems)) { emit(state.copyWith( errorMessage: 'Cannot have delay as the first action', @@ -963,7 +1063,8 @@ class RoutineBloc extends Bloc { try { final conditions = state.ifItems .map((item) { - final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -983,7 +1084,8 @@ class RoutineBloc extends Bloc { final actions = state.thenItems .map((item) { - final functions = state.selectedFunctions[item['uniqueCustomId']] ?? []; + final functions = + state.selectedFunctions[item['uniqueCustomId']] ?? []; if (functions.isEmpty) return null; final function = functions.first; @@ -993,8 +1095,9 @@ class RoutineBloc extends Bloc { executorProperty: ExecutorProperty( functionCode: function.functionCode, functionValue: function.value, - delaySeconds: - function.functionCode == 'delay' ? (function.value as num).toInt() : null, + delaySeconds: function.functionCode == 'delay' + ? (function.value as num).toInt() + : null, ), ); }) @@ -1005,12 +1108,14 @@ class RoutineBloc extends Bloc { spaceUuid: spaceId, automationName: state.routineName ?? '', decisionExpr: state.selectedAutomationOperator, - effectiveTime: state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), + effectiveTime: + state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), conditions: conditions, actions: actions, ); - await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? ''); + await SceneApi.updateAutomation( + createAutomationModel, state.automationId ?? ''); add(const LoadAutomation(spaceId)); emit(state.copyWith(isLoading: false)); @@ -1021,4 +1126,10 @@ class RoutineBloc extends Bloc { )); } } + + FutureOr _onSetAutomationActionExecutor( + SetAutomationActionExecutor event, Emitter emit) { + emit(state.copyWith( + automationActionExecutor: event.automationActionExecutor)); + } } diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart index 347b0bde..9c19028f 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_event.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_event.dart @@ -181,6 +181,13 @@ class UpdateAutomation extends RoutineEvent { List get props => []; } +class SetAutomationActionExecutor extends RoutineEvent { + final String automationActionExecutor; + const SetAutomationActionExecutor({required this.automationActionExecutor}); + @override + List get props => [automationActionExecutor]; +} + class FetchDevicesInRoutine extends RoutineEvent {} class ResetRoutineState extends RoutineEvent {} diff --git a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart index 3baff100..cd097d46 100644 --- a/lib/pages/routiens/bloc/routine_bloc/routine_state.dart +++ b/lib/pages/routiens/bloc/routine_bloc/routine_state.dart @@ -22,6 +22,7 @@ class RoutineState extends Equatable { final String? automationId; final bool? isUpdate; final List devices; + final String? automationActionExecutor; const RoutineState({ this.ifItems = const [], @@ -45,6 +46,7 @@ class RoutineState extends Equatable { this.automationId, this.isUpdate, this.devices = const [], + this.automationActionExecutor, }); RoutineState copyWith({ @@ -68,6 +70,7 @@ class RoutineState extends Equatable { String? automationId, bool? isUpdate, List? devices, + String? automationActionExecutor, }) { return RoutineState( ifItems: ifItems ?? this.ifItems, @@ -93,6 +96,8 @@ class RoutineState extends Equatable { automationId: automationId ?? this.automationId, isUpdate: isUpdate ?? this.isUpdate, devices: devices ?? this.devices, + automationActionExecutor: + automationActionExecutor ?? this.automationActionExecutor, ); } @@ -116,7 +121,8 @@ class RoutineState extends Equatable { effectiveTime, sceneId, automationId, - isUpdate, + isUpdate, devices, + automationActionExecutor, ]; } diff --git a/lib/pages/routiens/helper/save_routine_helper.dart b/lib/pages/routiens/helper/save_routine_helper.dart index d506783e..e7f29763 100644 --- a/lib/pages/routiens/helper/save_routine_helper.dart +++ b/lib/pages/routiens/helper/save_routine_helper.dart @@ -163,8 +163,6 @@ class SaveRoutineHelper { .add(const CreateSceneEvent()); } } - - Navigator.pop(context, true); }, isConfirmEnabled: true, ), diff --git a/lib/pages/routiens/models/routine_details_model.dart b/lib/pages/routiens/models/routine_details_model.dart index ec5f54fe..169bd237 100644 --- a/lib/pages/routiens/models/routine_details_model.dart +++ b/lib/pages/routiens/models/routine_details_model.dart @@ -48,7 +48,8 @@ class RoutineDetailsModel { spaceUuid: spaceUuid, automationName: name, decisionExpr: decisionExpr, - effectiveTime: effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), + effectiveTime: + effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''), conditions: conditions?.map((c) => c.toCondition()).toList() ?? [], actions: actions.map((a) => a.toAutomationAction()).toList(), ); @@ -63,7 +64,8 @@ class RoutineDetailsModel { if (iconId != null) 'iconUuid': iconId, if (showInDevice != null) 'showInDevice': showInDevice, if (effectiveTime != null) 'effectiveTime': effectiveTime!.toMap(), - if (conditions != null) 'conditions': conditions!.map((x) => x.toMap()).toList(), + if (conditions != null) + 'conditions': conditions!.map((x) => x.toMap()).toList(), if (type != null) 'type': type, if (sceneId != null) 'sceneId': sceneId, if (automationId != null) 'automationId': automationId, @@ -80,10 +82,12 @@ class RoutineDetailsModel { ), iconId: map['iconUuid'], showInDevice: map['showInDevice'], - effectiveTime: - map['effectiveTime'] != null ? EffectiveTime.fromMap(map['effectiveTime']) : null, + effectiveTime: map['effectiveTime'] != null + ? EffectiveTime.fromMap(map['effectiveTime']) + : null, conditions: map['conditions'] != null - ? List.from(map['conditions'].map((x) => RoutineCondition.fromMap(x))) + ? List.from( + map['conditions'].map((x) => RoutineCondition.fromMap(x))) : null, type: map['type'], sceneId: map['sceneId'], @@ -136,7 +140,8 @@ class RoutineAction { 'actionExecutor': actionExecutor, if (type != null) 'type': type, if (name != null) 'name': name, - if (executorProperty != null) 'executorProperty': executorProperty!.toMap(), + if (executorProperty != null) + 'executorProperty': executorProperty!.toMap(), }; } diff --git a/lib/pages/routiens/view/create_new_routine_view.dart b/lib/pages/routiens/view/create_new_routine_view.dart index d80ce5fb..dedfada8 100644 --- a/lib/pages/routiens/view/create_new_routine_view.dart +++ b/lib/pages/routiens/view/create_new_routine_view.dart @@ -68,7 +68,7 @@ class CreateNewRoutineView extends StatelessWidget { width: double.infinity, color: ColorsManager.dialogBlueTitle, ), - + /// THEN Container Expanded( child: Card( diff --git a/lib/pages/routiens/view/routines_view.dart b/lib/pages/routiens/view/routines_view.dart index 62f6f84f..45b71c0b 100644 --- a/lib/pages/routiens/view/routines_view.dart +++ b/lib/pages/routiens/view/routines_view.dart @@ -26,7 +26,9 @@ class _RoutinesViewState extends State { return BlocBuilder( builder: (context, state) { if (state is ShowCreateRoutineState && state.showCreateRoutine) { - return const CreateNewRoutineView(); + return const CreateNewRoutineView( + + ); } return Padding( padding: const EdgeInsets.all(16), diff --git a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart index 06995882..16a181b6 100644 --- a/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart +++ b/lib/pages/routiens/widgets/routine_dialogs/automation_dialog.dart @@ -7,7 +7,7 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart'; import 'package:syncrow_web/pages/routiens/widgets/dialog_footer.dart'; import 'package:syncrow_web/utils/constants/assets.dart'; -class AutomationDialog extends StatefulWidget { +class AutomationDialog extends StatelessWidget { final String automationName; final String automationId; final String uniqueCustomId; @@ -19,77 +19,86 @@ class AutomationDialog extends StatefulWidget { required this.uniqueCustomId, }); - @override - _AutomationDialogState createState() => _AutomationDialogState(); -} - -class _AutomationDialogState extends State { - bool _isEnabled = true; - @override Widget build(BuildContext context) { - return Dialog( - shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), - child: Container( - width: 400, - padding: const EdgeInsets.all(16), - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - DialogHeader(widget.automationName), - const SizedBox(height: 16), - ListTile( - leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24), - title: const Text('Enable'), - trailing: Radio( - value: true, - groupValue: _isEnabled, - onChanged: (bool? value) { - setState(() { - _isEnabled = value!; - }); - }, - ), - ), - ListTile( - leading: - SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24), - title: const Text('Disable'), - trailing: Radio( - value: false, - groupValue: _isEnabled, - onChanged: (bool? value) { - setState(() { - _isEnabled = value!; - }); - }, - ), - ), - const SizedBox(height: 16), - DialogFooter( - onConfirm: () { - context.read().add( - AddFunctionToRoutine( - [ - DeviceFunctionData( - entityId: widget.automationId, - functionCode: 'automation', - value: _isEnabled ? 'rule_enable' : 'rule_disable', - operationName: 'Automation', + return BlocBuilder( + builder: (context, state) { + final isEnabled = state.automationActionExecutor == 'rule_enable'; + + return Dialog( + shape: + RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)), + child: Container( + width: 400, + padding: const EdgeInsets.all(16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DialogHeader(automationName), + const SizedBox(height: 16), + ListTile( + leading: + SvgPicture.asset(Assets.acPower, width: 24, height: 24), + title: const Text('Enable'), + trailing: Radio( + value: true, + groupValue: isEnabled, + onChanged: (bool? value) { + if (value == true) { + context.read().add( + const SetAutomationActionExecutor( + automationActionExecutor: 'rule_enable', + ), + ); + } + }, + ), + ), + ListTile( + leading: SvgPicture.asset(Assets.acPowerOff, + width: 24, height: 24), + title: const Text('Disable'), + trailing: Radio( + value: false, + groupValue: isEnabled, + onChanged: (bool? value) { + if (value == false) { + context.read().add( + const SetAutomationActionExecutor( + automationActionExecutor: 'rule_disable', + ), + ); + } + }, + ), + ), + const SizedBox(height: 16), + DialogFooter( + onConfirm: () { + context.read().add( + AddFunctionToRoutine( + [ + DeviceFunctionData( + entityId: automationId, + functionCode: 'automation', + value: state.automationActionExecutor, + operationName: 'Automation', + ), + ], + uniqueCustomId, ), - ], - widget.uniqueCustomId, - ), - ); - Navigator.of(context).pop(true); - }, - onCancel: () => Navigator.of(context).pop(false), - isConfirmEnabled: true, - dialogWidth: 400, + ); + Navigator.of(context).pop(true); + }, + onCancel: () => Navigator.of(context).pop(false), + isConfirmEnabled: true, + dialogWidth: 400, + ), + ], ), - ], - ), - ), + ), + ); + }, ); } } diff --git a/lib/pages/routiens/widgets/routine_search_and_buttons.dart b/lib/pages/routiens/widgets/routine_search_and_buttons.dart index e1c567df..f458d17e 100644 --- a/lib/pages/routiens/widgets/routine_search_and_buttons.dart +++ b/lib/pages/routiens/widgets/routine_search_and_buttons.dart @@ -10,14 +10,41 @@ 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 RoutineSearchAndButtons extends StatelessWidget { +class RoutineSearchAndButtons extends StatefulWidget { const RoutineSearchAndButtons({ super.key, }); + @override + State createState() => + _RoutineSearchAndButtonsState(); +} + +class _RoutineSearchAndButtonsState extends State { + late TextEditingController _nameController; + + @override + void initState() { + super.initState(); + _nameController = TextEditingController(); + } + + @override + void dispose() { + _nameController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { - return BlocBuilder( + return BlocConsumer( + listenWhen: (previous, current) => + previous.routineName != current.routineName, + listener: (context, state) { + if (state.routineName != _nameController.text) { + _nameController.text = state.routineName ?? ''; + } + }, builder: (context, state) { return LayoutBuilder( builder: (BuildContext context, BoxConstraints constraints) { @@ -35,26 +62,9 @@ class RoutineSearchAndButtons extends StatelessWidget { children: [ ConstrainedBox( constraints: BoxConstraints( - maxWidth: - constraints.maxWidth > 700 ? 450 : constraints.maxWidth - 32), - // child: StatefulTextField( - // title: 'Routine Name', - // initialValue: state.routineName ?? '', - // height: 40, - // // controller: TextEditingController(), - // hintText: 'Please enter the name', - // boxDecoration: containerWhiteDecoration, - // elevation: 0, - // borderRadius: 15, - // isRequired: true, - // width: 450, - // onSubmitted: (value) { - // // context.read().add(SetRoutineName(value)); - // }, - // onChanged: (value) { - // context.read().add(SetRoutineName(value)); - // }, - // ), + maxWidth: constraints.maxWidth > 700 + ? 450 + : constraints.maxWidth - 32), child: Column( mainAxisAlignment: MainAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start, @@ -63,10 +73,13 @@ class RoutineSearchAndButtons extends StatelessWidget { children: [ Text('* ', style: context.textTheme.bodyMedium! - .copyWith(color: ColorsManager.red, fontSize: 13)), + .copyWith( + color: ColorsManager.red, + fontSize: 13)), Text( 'Routine Name', - style: context.textTheme.bodyMedium!.copyWith( + style: context.textTheme.bodyMedium! + .copyWith( fontSize: 13, fontWeight: FontWeight.w600, color: ColorsManager.blackColor, @@ -80,18 +93,24 @@ class RoutineSearchAndButtons extends StatelessWidget { decoration: containerWhiteDecoration, child: TextFormField( style: context.textTheme.bodyMedium! - .copyWith(color: ColorsManager.blackColor), - initialValue: state.routineName, + .copyWith( + color: ColorsManager.blackColor), + controller: _nameController, decoration: InputDecoration( hintText: 'Please enter the name', hintStyle: context.textTheme.bodyMedium! - .copyWith(fontSize: 12, color: ColorsManager.grayColor), + .copyWith( + fontSize: 12, + color: ColorsManager.grayColor), contentPadding: - const EdgeInsets.symmetric(horizontal: 12, vertical: 10), + const EdgeInsets.symmetric( + horizontal: 12, vertical: 10), border: InputBorder.none, ), onChanged: (value) { - context.read().add(SetRoutineName(value)); + context + .read() + .add(SetRoutineName(value)); }, validator: (value) { if (value == null || value.isEmpty) { @@ -111,15 +130,17 @@ class RoutineSearchAndButtons extends StatelessWidget { width: 200, child: Center( child: DefaultButton( - onPressed: state.isAutomation || state.isTabToRun + onPressed: state.isAutomation || + state.isTabToRun ? () async { - final result = await SettingHelper.showSettingDialog( + final result = await SettingHelper + .showSettingDialog( context: context, + iconId: state.selectedIcon, ); if (result != null) { - context - .read() - .add(AddSelectedIcon(result)); + context.read().add( + AddSelectedIcon(result)); } } : null, @@ -175,10 +196,12 @@ class RoutineSearchAndButtons extends StatelessWidget { child: Center( child: DefaultButton( onPressed: () async { - if (state.routineName == null || state.routineName!.isEmpty) { + if (state.routineName == null || + state.routineName!.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text('Please enter the routine name'), + content: const Text( + 'Please enter the routine name'), duration: const Duration(seconds: 2), backgroundColor: ColorsManager.red, action: SnackBarAction( @@ -192,10 +215,12 @@ class RoutineSearchAndButtons extends StatelessWidget { return; } - if (state.ifItems.isEmpty || state.thenItems.isEmpty) { + if (state.ifItems.isEmpty || + state.thenItems.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text('Please add if and then condition'), + content: const Text( + 'Please add if and then condition'), duration: const Duration(seconds: 2), backgroundColor: ColorsManager.red, action: SnackBarAction( @@ -208,13 +233,15 @@ class RoutineSearchAndButtons extends StatelessWidget { ); return; } - final result = - await SaveRoutineHelper.showSaveRoutineDialog(context); + final result = await SaveRoutineHelper + .showSaveRoutineDialog(context); if (result != null && result) { - BlocProvider.of(context).add( + BlocProvider.of(context) + .add( const CreateNewRoutineViewEvent(false), ); - BlocProvider.of(context).add( + BlocProvider.of(context) + .add( const TriggerSwitchTabsEvent(true), ); } @@ -248,11 +275,15 @@ class RoutineSearchAndButtons extends StatelessWidget { child: DefaultButton( onPressed: state.isAutomation || state.isTabToRun ? () async { - final result = await SettingHelper.showSettingDialog( + final result = + await SettingHelper.showSettingDialog( context: context, + iconId: state.selectedIcon, ); if (result != null) { - context.read().add(AddSelectedIcon(result)); + context + .read() + .add(AddSelectedIcon(result)); } } : null, @@ -302,10 +333,12 @@ class RoutineSearchAndButtons extends StatelessWidget { child: Center( child: DefaultButton( onPressed: () async { - if (state.routineName == null || state.routineName!.isEmpty) { + if (state.routineName == null || + state.routineName!.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text('Please enter the routine name'), + content: const Text( + 'Please enter the routine name'), duration: const Duration(seconds: 2), backgroundColor: ColorsManager.red, action: SnackBarAction( @@ -319,10 +352,12 @@ class RoutineSearchAndButtons extends StatelessWidget { return; } - if (state.ifItems.isEmpty || state.thenItems.isEmpty) { + if (state.ifItems.isEmpty || + state.thenItems.isEmpty) { ScaffoldMessenger.of(context).showSnackBar( SnackBar( - content: const Text('Please add if and then condition'), + content: const Text( + 'Please add if and then condition'), duration: const Duration(seconds: 2), backgroundColor: ColorsManager.red, action: SnackBarAction( @@ -335,7 +370,9 @@ class RoutineSearchAndButtons extends StatelessWidget { ); return; } - final result = await SaveRoutineHelper.showSaveRoutineDialog(context); + final result = + await SaveRoutineHelper.showSaveRoutineDialog( + context); if (result != null && result) { BlocProvider.of(context).add( const CreateNewRoutineViewEvent(false), diff --git a/lib/pages/routiens/widgets/then_container.dart b/lib/pages/routiens/widgets/then_container.dart index db3da8be..d959c372 100644 --- a/lib/pages/routiens/widgets/then_container.dart +++ b/lib/pages/routiens/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,57 +60,76 @@ 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'], + 'imagePath': + Assets.automation, + 'title': state + .thenItems[index]['name'], })); } return; } - final result = await DeviceDialogHelper.showDeviceDialog( - context, state.thenItems[index], - removeComparetors: true); + final result = await DeviceDialogHelper + .showDeviceDialog( + context, state.thenItems[index], + removeComparetors: true); if (result != null) { - context - .read() - .add(AddToThenContainer(state.thenItems[index])); + 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])); + .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'])); }, ), ))), @@ -157,7 +183,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({ @@ -169,11 +196,13 @@ class ThenContainer extends StatelessWidget { return; } - final result = await DeviceDialogHelper.showDeviceDialog(context, mutableData, + final result = await DeviceDialogHelper.showDeviceDialog( + context, mutableData, removeComparetors: true); if (result != null) { context.read().add(AddToThenContainer(mutableData)); - } else if (!['AC', '1G', '2G', '3G'].contains(mutableData['productType'])) { + } else if (!['AC', '1G', '2G', '3G'] + .contains(mutableData['productType'])) { context.read().add(AddToThenContainer(mutableData)); } }, diff --git a/lib/services/routines_api.dart b/lib/services/routines_api.dart index 5b5d7984..ffc8a8c7 100644 --- a/lib/services/routines_api.dart +++ b/lib/services/routines_api.dart @@ -36,7 +36,6 @@ class SceneApi { static Future> createAutomation( CreateAutomationModel createAutomationModel) async { try { - debugPrint("automation body ${createAutomationModel.toMap()}"); final response = await _httpService.post( path: ApiEndpoints.createAutomation, body: createAutomationModel.toMap(),