Merge pull request #57 from SyncrowIOT/routines_issues

Routines issues
This commit is contained in:
Abdullah
2024-12-03 01:04:12 +03:00
committed by GitHub
11 changed files with 657 additions and 391 deletions

View File

@ -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/snack_bar.dart';
import 'package:uuid/uuid.dart';
part 'routine_event.dart';
@ -44,16 +45,17 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
on<FetchDevicesInRoutine>(_fetchDevices);
on<UpdateScene>(_onUpdateScene);
on<UpdateAutomation>(_onUpdateAutomation);
on<SetAutomationActionExecutor>(_onSetAutomationActionExecutor);
on<TriggerSwitchTabsEvent>(_triggerSwitchTabsEvent);
on<CreateNewRoutineViewEvent>(_createNewRoutineViewEvent);
on<ResetErrorMessage>(_resetErrorMessage);
}
FutureOr<void> _triggerSwitchTabsEvent(
TriggerSwitchTabsEvent event,
Emitter<RoutineState> 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(spaceId, communityId));
@ -61,6 +63,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
_resetErrorMessage(
ResetErrorMessage event,
Emitter<RoutineState> emit,
) {
emit(state.copyWith(errorMessage: ''));
}
FutureOr<void> _createNewRoutineViewEvent(
CreateNewRoutineViewEvent event,
Emitter<RoutineState> emit,
@ -72,8 +81,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
final updatedIfItems = List<Map<String, dynamic>>.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;
@ -82,18 +91,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
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<RoutineState> emit) {
void _onAddToThenContainer(
AddToThenContainer event, Emitter<RoutineState> emit) {
final currentItems = List<Map<String, dynamic>>.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;
@ -104,22 +116,26 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
emit(state.copyWith(thenItems: currentItems));
}
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
void _onAddFunctionsToRoutine(
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
try {
if (event.functions.isEmpty) return;
List<DeviceFunctionData> selectedFunction = List<DeviceFunctionData>.from(event.functions);
List<DeviceFunctionData> selectedFunction =
List<DeviceFunctionData>.from(event.functions);
Map<String, List<DeviceFunctionData>> currentSelectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
List<DeviceFunctionData> currentFunctions =
List<DeviceFunctionData>.from(currentSelectedFunctions[event.uniqueCustomId] ?? []);
List<DeviceFunctionData>.from(
currentSelectedFunctions[event.uniqueCustomId] ?? []);
List<String> 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);
@ -129,13 +145,15 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
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));
@ -144,11 +162,13 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onLoadScenes(LoadScenes event, Emitter<RoutineState> emit) async {
Future<void> _onLoadScenes(
LoadScenes event, Emitter<RoutineState> 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,
@ -163,7 +183,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onLoadAutomation(LoadAutomation event, Emitter<RoutineState> emit) async {
Future<void> _onLoadAutomation(
LoadAutomation event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true, errorMessage: null));
try {
@ -191,41 +212,45 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onSearchRoutines(SearchRoutines event, Emitter<RoutineState> emit) async {
FutureOr<void> _onSearchRoutines(
SearchRoutines event, Emitter<RoutineState> 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<void> _onAddSelectedIcon(AddSelectedIcon event, Emitter<RoutineState> emit) {
FutureOr<void> _onAddSelectedIcon(
AddSelectedIcon event, Emitter<RoutineState> emit) {
emit(state.copyWith(selectedIcon: event.icon));
}
bool _isFirstActionDelay(List<Map<String, dynamic>> actions) {
if (actions.isEmpty) return false;
return actions.first['deviceId'] == 'delay';
}
// bool _isFirstActionDelay(List<Map<String, dynamic>> actions) {
// if (actions.isEmpty) return false;
// return actions.first['deviceId'] == 'delay';
// }
bool _isLastActionDelay(List<Map<String, dynamic>> actions) {
if (actions.isEmpty) return false;
return actions.last['deviceId'] == 'delay';
}
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateScene(
CreateSceneEvent event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
if (_isFirstActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action',
isLoading: false,
));
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',
errorMessage:
'A delay condition cannot be the only or the last action',
isLoading: false,
));
return;
@ -238,6 +263,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return functions.map((function) {
if (function.functionCode == 'automation') {
return CreateSceneAction(
actionType: 'automation',
entityId: function.entityId,
actionExecutor: function.value,
executorProperty: null,
@ -257,6 +283,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
return CreateSceneAction(
actionType: 'scene',
entityId: function.entityId,
actionExecutor: 'device_issue',
executorProperty: CreateSceneExecutorProperty(
@ -296,27 +323,33 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onCreateAutomation(CreateAutomationEvent event, Emitter<RoutineState> emit) async {
Future<void> _onCreateAutomation(
CreateAutomationEvent event, Emitter<RoutineState> emit) async {
try {
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
errorMessage: 'Automation name is required',
));
CustomSnackBar.redSnackBar('Automation name is required');
return;
}
if (_isFirstActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action',
isLoading: false,
));
return;
}
// if (_isFirstActionDelay(state.thenItems)) {
// emit(state.copyWith(
// errorMessage: 'Cannot have delay as the first action',
// isLoading: false,
// ));
// CustomSnackBar.redSnackBar('Cannot have delay as the first action');
// return;
// }
if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'Cannot have delay as 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');
return;
}
emit(state.copyWith(isLoading: true, errorMessage: null));
@ -349,12 +382,25 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
final actions = state.thenItems.expand((item) {
if (item['type'] == 'tap_to_run') {
return [
AutomationAction(
actionType: 'scene',
entityId: item['deviceId'],
actionExecutor: 'rule_trigger',
),
];
}
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) {
if (function.functionCode == 'automation') {
return AutomationAction(
actionType: 'automation',
entityId: function.entityId,
actionExecutor: function.value,
executorProperty: null,
);
}
@ -402,26 +448,32 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isLoading: false,
errorMessage: result['message'],
));
CustomSnackBar.redSnackBar('Something went wrong');
}
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Something went wrong',
));
CustomSnackBar.redSnackBar('Something went wrong');
}
}
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
FutureOr<void> _onRemoveDragCard(
RemoveDragCard event, Emitter<RoutineState> emit) {
if (event.isFromThen) {
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
final selectedFunctions =
Map<String, List<DeviceFunctionData>>.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<Map<String, dynamic>>.from(state.ifItems);
final selectedFunctions = Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
final selectedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems.removeAt(event.index);
selectedFunctions.remove(event.key);
@ -432,7 +484,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
isAutomation: false,
isTabToRun: false));
} else {
emit(state.copyWith(ifItems: ifItems, selectedFunctions: selectedFunctions));
emit(state.copyWith(
ifItems: ifItems, selectedFunctions: selectedFunctions));
}
}
}
@ -444,18 +497,23 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
));
}
FutureOr<void> _onEffectiveTimeEvent(EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
FutureOr<void> _onEffectiveTimeEvent(
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
emit(state.copyWith(effectiveTime: event.effectiveTime));
}
FutureOr<void> _onSetRoutineName(SetRoutineName event, Emitter<RoutineState> emit) {
FutureOr<void> _onSetRoutineName(
SetRoutineName event, Emitter<RoutineState> emit) {
emit(state.copyWith(
routineName: event.name,
));
}
(List<Map<String, dynamic>>, List<Map<String, dynamic>>, Map<String, List<DeviceFunctionData>>)
_createCardData(
(
List<Map<String, dynamic>>,
List<Map<String, dynamic>>,
Map<String, List<DeviceFunctionData>>
) _createCardData(
List<RoutineAction> actions,
List<RoutineCondition>? conditions,
Map<String, List<DeviceFunctionData>> currentFunctions,
@ -488,7 +546,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
'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;
@ -524,8 +583,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
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),
};
@ -568,7 +630,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return (thenItems, ifItems, currentFunctions);
}
Future<void> _onGetSceneDetails(GetSceneDetails event, Emitter<RoutineState> emit) async {
Future<void> _onGetSceneDetails(
GetSceneDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
@ -616,8 +679,10 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (!deviceCards.containsKey(deviceId)) {
deviceCards[deviceId] = {
'entityId': action.entityId,
'deviceId': action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId': action.type == 'automation' || action.actionExecutor == 'delay'
'deviceId':
action.actionExecutor == 'delay' ? 'delay' : action.entityId,
'uniqueCustomId':
action.type == 'automation' || action.actionExecutor == 'delay'
? const Uuid().v4()
: action.entityId,
'title': action.actionExecutor == 'delay'
@ -653,8 +718,9 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
operationName: 'Automation',
),
);
emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null && action.actionExecutor != 'delay') {
// emit(state.copyWith(automationActionExecutor: action.actionExecutor));
} else if (action.executorProperty != null &&
action.actionExecutor != 'delay') {
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
@ -726,153 +792,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
Future<void> _onGetAutomationDetails(
GetAutomationDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
isUpdate: true,
isTabToRun: false,
automationId: event.automationId,
isAutomation: true,
ifItems: [],
thenItems: [],
));
final automationDetails = await SceneApi.getAutomationDetails(event.automationId);
final List<Map<String, dynamic>> thenItems;
final List<Map<String, dynamic>> ifItems;
final Map<String, List<DeviceFunctionData>> updatedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
ifItems = automationDetails.conditions?.map((condition) {
late AllDevicesModel? matchingDevice;
for (var device in state.devices) {
if (device.uuid == condition.entityId) {
matchingDevice = device;
break;
}
}
final cardData = {
'entityId': condition.entityId,
'uniqueCustomId': const Uuid().v4(),
'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) ?? '',
'device': matchingDevice,
};
final functions = matchingDevice?.functions ?? [];
for (var function in functions) {
if (function.code == condition.expr.statusCode) {
updatedFunctions[cardData['uniqueCustomId'].toString()] = [
DeviceFunctionData(
entityId: condition.entityId,
functionCode: condition.expr.statusCode,
value: condition.expr.statusValue,
operationName: function.operationName,
),
];
break;
}
}
return cardData;
}).toList() ??
[];
// Create then items from actions
thenItems = automationDetails.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,
'functions': matchingDevice.functions,
'imagePath': matchingDevice.getDefaultIcon(action.productType),
'device': matchingDevice,
};
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;
}
}
} 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,
),
];
} else if (action.actionExecutor == 'rule_disable' ||
action.actionExecutor == 'rule_enable') {
updatedFunctions[cardData['uniqueCustomId'].toString()] = [
DeviceFunctionData(
entityId: action.entityId,
functionCode: 'automation',
value: action.actionExecutor,
operationName: action.name ?? 'Automation',
),
];
}
return cardData;
}).toList();
emit(state.copyWith(
isLoading: false,
routineName: automationDetails.name,
selectedAutomationOperator: automationDetails.decisionExpr,
effectiveTime: automationDetails.effectiveTime,
isAutomation: true,
thenItems: thenItems,
ifItems: ifItems,
selectedFunctions: updatedFunctions,
automationId: automationDetails.automationId,
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to load automation details: $e',
));
}
}
FutureOr<void> _onResetRoutineState(ResetRoutineState event, Emitter<RoutineState> emit) {
FutureOr<void> _onResetRoutineState(
ResetRoutineState event, Emitter<RoutineState> emit) {
emit(state.copyWith(
ifItems: [],
thenItems: [],
@ -900,7 +821,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
if (state.isTabToRun) {
SceneApi.deleteScene(unitUuid: spaceId, sceneId: state.sceneId ?? '');
} else {
SceneApi.deleteAutomation(unitUuid: spaceId, automationId: state.automationId ?? '');
SceneApi.deleteAutomation(
unitUuid: spaceId, automationId: state.automationId ?? '');
}
add(const LoadScenes(spaceId, communityId));
@ -929,7 +851,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
// }
// }
FutureOr<void> _fetchDevices(FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
FutureOr<void> _fetchDevices(
FetchDevicesInRoutine event, Emitter<RoutineState> emit) async {
emit(state.copyWith(isLoading: true));
try {
final devices = await DevicesManagementApi().fetchDevices();
@ -940,21 +863,23 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onUpdateScene(UpdateScene event, Emitter<RoutineState> emit) async {
FutureOr<void> _onUpdateScene(
UpdateScene event, Emitter<RoutineState> emit) async {
try {
// Check if first action is delay
if (_isFirstActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action',
isLoading: false,
));
// if (_isFirstActionDelay(state.thenItems)) {
// emit(state.copyWith(
// errorMessage: 'Cannot have delay as the first action',
// isLoading: false,
// ));
return;
}
// return;
// }
if (_isLastActionDelay(state.thenItems)) {
emit(state.copyWith(
errorMessage: 'Cannot have delay as the last action',
errorMessage:
'A delay condition cannot be the only or the last action',
isLoading: false,
));
return;
@ -967,6 +892,7 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
return functions.map((function) {
if (function.functionCode == 'automation') {
return CreateSceneAction(
actionType: 'automation',
entityId: function.entityId,
actionExecutor: function.value,
executorProperty: null,
@ -1006,7 +932,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
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(spaceId, communityId));
@ -1025,72 +952,125 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
}
}
FutureOr<void> _onUpdateAutomation(UpdateAutomation event, Emitter<RoutineState> emit) async {
if (_isFirstActionDelay(state.thenItems)) {
FutureOr<void> _onUpdateAutomation(
UpdateAutomation event, Emitter<RoutineState> emit) async {
try {
if (state.routineName == null || state.routineName!.isEmpty) {
emit(state.copyWith(
errorMessage: 'Cannot have delay as the first action',
errorMessage: 'Automation name is required',
));
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));
try {
final conditions = state.ifItems
.map((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
if (functions.isEmpty) return null;
emit(state.copyWith(isLoading: true, errorMessage: null));
final function = functions.first;
final conditions = state.ifItems.expand((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
return functions.map((function) {
return Condition(
code: state.ifItems.indexOf(item) + 1,
code: state.selectedFunctions[item['uniqueCustomId']]!.indexOf(
function,
) +
1,
entityId: function.entityId,
entityType: item['productType'],
entityType: 'device_report',
expr: ConditionExpr(
statusCode: function.functionCode,
statusValue: function.value,
comparator: function.condition ?? '==',
statusValue: function.value,
),
);
})
.whereType<Condition>()
.toList();
});
}).toList();
if (conditions.isEmpty) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'At least one condition is required',
));
return;
}
final actions = state.thenItems.expand((item) {
if (item['type'] == 'tap_to_run' || item['type'] == 'scene') {
return [
AutomationAction(
actionType: 'scene',
entityId: item['deviceId'],
actionExecutor: 'rule_trigger',
),
];
}
final actions = state.thenItems
.map((item) {
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
if (functions.isEmpty) return null;
final function = functions.first;
return functions.map((function) {
if (function.functionCode == 'automation') {
return AutomationAction(
actionType: 'automation',
entityId: function.entityId,
actionExecutor: function.value,
executorProperty: null,
);
}
if (item['deviceId'] == 'delay') {
return AutomationAction(
entityId: function.entityId,
actionExecutor: function.actionExecutor,
actionType: 'automation',
actionExecutor: 'delay',
executorProperty: ExecutorProperty(
delaySeconds: int.tryParse(function.value.toString()) ?? 0,
),
);
}
return AutomationAction(
entityId: function.entityId,
actionExecutor: 'device_issue',
actionType: 'automation',
executorProperty: ExecutorProperty(
functionCode: function.functionCode,
functionValue: function.value,
delaySeconds:
function.functionCode == 'delay' ? (function.value as num).toInt() : null,
),
);
})
.whereType<AutomationAction>()
.toList();
});
}).toList();
final createAutomationModel = CreateAutomationModel(
spaceUuid: spaceId,
automationName: state.routineName ?? '',
decisionExpr: state.selectedAutomationOperator,
effectiveTime: state.effectiveTime ?? EffectiveTime(start: '', end: '', loops: ''),
effectiveTime: EffectiveTime(
start: state.effectiveTime?.start ?? '00:00',
end: state.effectiveTime?.end ?? '23:59',
loops: state.effectiveTime?.loops ?? '1111111',
),
conditions: conditions,
actions: actions,
);
final result =
await SceneApi.updateAutomation(createAutomationModel, state.automationId ?? '');
final result = await SceneApi.updateAutomation(
createAutomationModel, state.automationId ?? '');
if (result['success']) {
add(ResetRoutineState());
add(const LoadScenes(spaceId, communityId));
add(const LoadAutomation(spaceId));
add(const LoadScenes(spaceId, communityId));
} else {
emit(state.copyWith(
isLoading: false,
@ -1100,13 +1080,198 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to update automation: $e',
errorMessage: 'Something went wrong',
));
}
}
FutureOr<void> _onSetAutomationActionExecutor(
SetAutomationActionExecutor event, Emitter<RoutineState> emit) {
emit(state.copyWith(automationActionExecutor: event.automationActionExecutor));
Future<void> _onGetAutomationDetails(
GetAutomationDetails event, Emitter<RoutineState> emit) async {
try {
emit(state.copyWith(
isLoading: true,
isUpdate: true,
isTabToRun: false,
automationId: event.automationId,
isAutomation: true,
ifItems: [],
thenItems: [],
));
final automationDetails =
await SceneApi.getAutomationDetails(event.automationId);
final Map<String, Map<String, dynamic>> deviceCards = {};
final Map<String, List<DeviceFunctionData>> updatedFunctions =
Map<String, List<DeviceFunctionData>>.from(state.selectedFunctions);
for (RoutineCondition condition in automationDetails.conditions ?? []) {
AllDevicesModel? matchingDevice = state.devices.firstWhere(
(device) => device.uuid == condition.entityId,
orElse: () => AllDevicesModel(
uuid: condition.entityId,
name: condition.entityId,
productType: condition.productType,
),
);
final deviceId = condition.entityId;
if (!deviceCards.containsKey(deviceId)) {
deviceCards[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',
};
}
final cardData = deviceCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
final functions = matchingDevice.functions;
for (var function in functions) {
if (function.code == condition.expr.statusCode) {
updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData(
entityId: condition.entityId,
functionCode: condition.expr.statusCode,
value: condition.expr.statusValue,
operationName: function.operationName,
),
);
break;
}
}
}
// Process actions (thenItems)
for (var action in automationDetails.actions) {
AllDevicesModel? matchingDevice = state.devices.firstWhere(
(device) => device.uuid == action.entityId,
orElse: () => AllDevicesModel(
uuid: action.entityId,
name: action.entityId,
productType: action.productType,
),
);
final deviceId = action.actionExecutor == 'delay'
? '${action.entityId}_delay'
: action.entityId;
if (!deviceCards.containsKey(deviceId)) {
deviceCards[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',
};
}
final cardData = deviceCards[deviceId]!;
final uniqueCustomId = cardData['uniqueCustomId'].toString();
if (!updatedFunctions.containsKey(uniqueCustomId)) {
updatedFunctions[uniqueCustomId] = [];
}
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[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[uniqueCustomId]!.add(
DeviceFunctionData(
entityId: action.entityId,
functionCode: 'delay',
value: action.executorProperty?.delaySeconds ?? 0,
operationName: delayFunction.operationName,
),
);
} else if (action.actionExecutor == 'rule_disable' ||
action.actionExecutor == 'rule_enable') {
updatedFunctions[uniqueCustomId]!.add(
DeviceFunctionData(
entityId: action.entityId,
functionCode: 'automation',
value: action.actionExecutor,
operationName: action.name ?? 'Automation',
),
);
}
}
final ifItems = deviceCards.values
.where((card) => card['type'] == 'condition')
.toList();
final thenItems = deviceCards.values
.where((card) =>
card['type'] == 'action' ||
card['type'] == 'automation' ||
card['type'] == 'scene')
.toList();
emit(state.copyWith(
isLoading: false,
routineName: automationDetails.name,
selectedAutomationOperator: automationDetails.decisionExpr,
effectiveTime: automationDetails.effectiveTime,
isAutomation: true,
thenItems: thenItems,
ifItems: ifItems,
selectedFunctions: updatedFunctions,
automationId: automationDetails.automationId,
));
} catch (e) {
emit(state.copyWith(
isLoading: false,
errorMessage: 'Failed to load automation details: $e',
));
}
}
}

View File

@ -206,3 +206,5 @@ class FetchDevicesInRoutine extends RoutineEvent {}
class ResetRoutineState extends RoutineEvent {}
class ClearFunctions extends RoutineEvent {}
class ResetErrorMessage extends RoutineEvent {}

View File

@ -22,7 +22,7 @@ class RoutineState extends Equatable {
final String? automationId;
final bool? isUpdate;
final List<AllDevicesModel> devices;
final String? automationActionExecutor;
// final String? automationActionExecutor;
final bool routineTab;
final bool createRoutineView;
@ -48,7 +48,7 @@ class RoutineState extends Equatable {
this.automationId,
this.isUpdate,
this.devices = const [],
this.automationActionExecutor,
// this.automationActionExecutor,
this.routineTab = false,
this.createRoutineView = false});
@ -73,7 +73,7 @@ class RoutineState extends Equatable {
String? automationId,
bool? isUpdate,
List<AllDevicesModel>? devices,
String? automationActionExecutor,
// String? automationActionExecutor,
TextEditingController? nameController,
bool? routineTab,
bool? createRoutineView,
@ -88,18 +88,21 @@ class RoutineState extends Equatable {
errorMessage: errorMessage ?? this.errorMessage,
routineName: routineName ?? this.routineName,
selectedIcon: selectedIcon ?? this.selectedIcon,
loadScenesErrorMessage: loadScenesErrorMessage ?? this.loadScenesErrorMessage,
loadAutomationErrorMessage: loadAutomationErrorMessage ?? this.loadAutomationErrorMessage,
loadScenesErrorMessage:
loadScenesErrorMessage ?? this.loadScenesErrorMessage,
loadAutomationErrorMessage:
loadAutomationErrorMessage ?? this.loadAutomationErrorMessage,
searchText: searchText ?? this.searchText,
isTabToRun: isTabToRun ?? this.isTabToRun,
isAutomation: isAutomation ?? this.isAutomation,
selectedAutomationOperator: selectedAutomationOperator ?? this.selectedAutomationOperator,
selectedAutomationOperator:
selectedAutomationOperator ?? this.selectedAutomationOperator,
effectiveTime: effectiveTime ?? this.effectiveTime,
sceneId: sceneId ?? this.sceneId,
automationId: automationId ?? this.automationId,
isUpdate: isUpdate ?? this.isUpdate,
devices: devices ?? this.devices,
automationActionExecutor: automationActionExecutor ?? this.automationActionExecutor,
// automationActionExecutor: automationActionExecutor ?? this.automationActionExecutor,
routineTab: routineTab ?? this.routineTab,
createRoutineView: createRoutineView ?? this.createRoutineView);
}
@ -126,7 +129,7 @@ class RoutineState extends Equatable {
automationId,
isUpdate,
devices,
automationActionExecutor,
// automationActionExecutor,
routineTab,
createRoutineView
];

View File

@ -1,3 +1,5 @@
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_svg/flutter_svg.dart';
@ -6,6 +8,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/color_manager.dart';
import 'package:syncrow_web/utils/constants/assets.dart';
import 'package:syncrow_web/utils/extension/build_context_x.dart';
class SaveRoutineHelper {
static Future<void> showSaveRoutineDialog(BuildContext context) async {
@ -98,18 +101,29 @@ class SaveRoutineHelper {
final functions =
state.selectedFunctions[item['uniqueCustomId']] ?? [];
return ListTile(
leading: SvgPicture.asset(
leading: item['type'] == 'tap_to_run'
? Image.memory(
base64Decode(item['icon']),
width: 22,
height: 22,
)
: SvgPicture.asset(
item['imagePath'],
width: 22,
height: 22,
),
title:
Text(item['title'], style: const TextStyle(fontSize: 14)),
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: const TextStyle(
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.grayColor, fontSize: 8),
overflow: TextOverflow.ellipsis,
maxLines: 3,
@ -124,17 +138,17 @@ 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),
),
),
// 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: () {
onConfirm: () async {
if (state.isAutomation) {
if (state.automationId != null) {
context.read<RoutineBloc>().add(const UpdateAutomation());
@ -148,10 +162,9 @@ class SaveRoutineHelper {
context.read<RoutineBloc>().add(const CreateSceneEvent());
}
}
if (context.read<RoutineBloc>().state.errorMessage == null ||
context.read<RoutineBloc>().state.errorMessage!.isEmpty) {
// if (state.errorMessage == null || state.errorMessage!.isEmpty) {
Navigator.pop(context);
}
// }
},
isConfirmEnabled: true,
),

View File

@ -137,11 +137,13 @@ class ConditionExpr {
class AutomationAction {
String entityId;
String? actionType;
String actionExecutor;
ExecutorProperty? executorProperty;
AutomationAction({
required this.entityId,
this.actionType,
required this.actionExecutor,
this.executorProperty,
});
@ -150,12 +152,15 @@ class AutomationAction {
return {
'entityId': entityId,
'actionExecutor': actionExecutor,
if (executorProperty != null)
'executorProperty': executorProperty?.toMap(),
'actionType': actionType
};
}
factory AutomationAction.fromMap(Map<String, dynamic> map) {
return AutomationAction(
actionType: map['actionType'],
entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '',
executorProperty: map['executorProperty'] != null

View File

@ -95,10 +95,12 @@ class CreateSceneModel {
class CreateSceneAction {
String entityId;
String? actionType;
String actionExecutor;
CreateSceneExecutorProperty? executorProperty;
CreateSceneAction({
this.actionType,
required this.entityId,
required this.actionExecutor,
required this.executorProperty,
@ -110,6 +112,7 @@ class CreateSceneAction {
CreateSceneExecutorProperty? executorProperty,
}) {
return CreateSceneAction(
actionType: actionType ?? this.actionType,
entityId: entityId ?? this.entityId,
actionExecutor: actionExecutor ?? this.actionExecutor,
executorProperty: executorProperty ?? this.executorProperty,
@ -125,6 +128,7 @@ class CreateSceneAction {
};
} else {
return {
"actionType": actionType,
'entityId': entityId,
'actionExecutor': actionExecutor,
};
@ -133,6 +137,7 @@ class CreateSceneAction {
factory CreateSceneAction.fromMap(Map<String, dynamic> map) {
return CreateSceneAction(
actionType: map['actionType'],
entityId: map['entityId'] ?? '',
actionExecutor: map['actionExecutor'] ?? '',
executorProperty:

View File

@ -35,6 +35,20 @@ class DraggableCard extends StatelessWidget {
final deviceFunctions =
state.selectedFunctions[deviceData['uniqueCustomId']] ?? [];
int index = state.thenItems.indexWhere(
(item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']);
if (index != -1) {
return _buildCardContent(context, deviceFunctions, padding: padding);
}
int ifIndex = state.ifItems.indexWhere(
(item) => item['uniqueCustomId'] == deviceData['uniqueCustomId']);
if (ifIndex != -1) {
return _buildCardContent(context, deviceFunctions, padding: padding);
}
return Draggable<Map<String, dynamic>>(
data: deviceData,
feedback: Transform.rotate(
@ -79,16 +93,12 @@ class DraggableCard extends StatelessWidget {
),
),
padding: const EdgeInsets.all(8),
child: imagePath.contains('.svg')
? SvgPicture.asset(
imagePath,
child: deviceData['type'] == 'tap_to_run'
? Image.memory(
base64Decode(deviceData['icon']),
)
: imagePath.contains('.png')
? Image.asset(
: SvgPicture.asset(
imagePath,
)
: Image.memory(
base64Decode(imagePath),
),
),
const SizedBox(height: 8),

View File

@ -7,98 +7,106 @@ 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 StatelessWidget {
class AutomationDialog extends StatefulWidget {
final String automationName;
final String automationId;
final String uniqueCustomId;
final String? passedAutomationActionExecutor;
const AutomationDialog({
super.key,
required this.automationName,
required this.automationId,
required this.uniqueCustomId,
this.passedAutomationActionExecutor,
});
@override
Widget build(BuildContext context) {
return BlocBuilder<RoutineBloc, RoutineState>(
builder: (context, state) {
final isEnabled = state.automationActionExecutor == 'rule_enable';
State<AutomationDialog> createState() => _AutomationDialogState();
}
class _AutomationDialogState extends State<AutomationDialog> {
String? selectedAutomationActionExecutor;
@override
void initState() {
super.initState();
List<DeviceFunctionData>? functions = context
.read<RoutineBloc>()
.state
.selectedFunctions[widget.uniqueCustomId];
for (DeviceFunctionData data in functions ?? []) {
if (data.entityId == widget.automationId) {
selectedAutomationActionExecutor = data.value;
}
}
}
@override
Widget build(BuildContext context) {
return Dialog(
shape:
RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(12)),
child: Container(
width: 400,
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
DialogHeader(automationName),
DialogHeader(widget.automationName),
const SizedBox(height: 16),
ListTile(
leading:
SvgPicture.asset(Assets.acPower, width: 24, height: 24),
leading: SvgPicture.asset(Assets.acPower, width: 24, height: 24),
title: const Text('Enable'),
trailing: Radio<bool>(
value: true,
groupValue: isEnabled,
onChanged: (bool? value) {
if (value == true) {
context.read<RoutineBloc>().add(
const SetAutomationActionExecutor(
automationActionExecutor: 'rule_enable',
),
);
}
},
),
trailing: Radio<String?>(
value: 'rule_enable',
groupValue: selectedAutomationActionExecutor,
onChanged: (String? value) {
setState(() {
selectedAutomationActionExecutor = 'rule_enable';
});
}),
),
ListTile(
leading: SvgPicture.asset(Assets.acPowerOff,
width: 24, height: 24),
leading:
SvgPicture.asset(Assets.acPowerOff, width: 24, height: 24),
title: const Text('Disable'),
trailing: Radio<bool>(
value: false,
groupValue: isEnabled,
onChanged: (bool? value) {
if (value == false) {
context.read<RoutineBloc>().add(
const SetAutomationActionExecutor(
automationActionExecutor: 'rule_disable',
),
);
}
trailing: Radio<String?>(
value: 'rule_disable',
groupValue: selectedAutomationActionExecutor,
onChanged: (String? value) {
setState(() {
selectedAutomationActionExecutor = 'rule_disable';
});
},
),
),
const SizedBox(height: 16),
DialogFooter(
onConfirm: () {
if (selectedAutomationActionExecutor != null) {
context.read<RoutineBloc>().add(
AddFunctionToRoutine(
[
DeviceFunctionData(
entityId: automationId,
entityId: widget.automationId,
functionCode: 'automation',
value: state.automationActionExecutor,
value: selectedAutomationActionExecutor,
operationName: 'Automation',
),
],
uniqueCustomId,
widget.uniqueCustomId,
),
);
}
Navigator.of(context).pop(true);
},
onCancel: () => Navigator.of(context).pop(false),
isConfirmEnabled: true,
onCancel: () => Navigator.of(context).pop(),
isConfirmEnabled: selectedAutomationActionExecutor != null,
dialogWidth: 400,
),
],
),
),
);
},
);
}
}

View File

@ -43,6 +43,13 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
return Wrap(
runSpacing: 16,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
state.errorMessage ?? '',
style: const TextStyle(color: Colors.red),
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
@ -214,6 +221,7 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
}
// final result =
// await
BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
SaveRoutineHelper.showSaveRoutineDialog(context);
// if (result != null && result) {
// BlocProvider.of<RoutineBloc>(context).add(
@ -341,6 +349,7 @@ class _RoutineSearchAndButtonsState extends State<RoutineSearchAndButtons> {
}
// final result =
// await
BlocProvider.of<RoutineBloc>(context).add(ResetErrorMessage());
SaveRoutineHelper.showSaveRoutineDialog(context);
// if (result != null && result) {
// BlocProvider.of<RoutineBloc>(context).add(

View File

@ -87,8 +87,11 @@ class ThenContainer extends StatelessWidget {
...state.thenItems[index],
'imagePath':
Assets.automation,
'title': state
.thenItems[index]['name'],
'title':
state.thenItems[index]
['name'] ??
state.thenItems[index]
['title'],
}));
}
return;
@ -149,6 +152,12 @@ class ThenContainer extends StatelessWidget {
}
if (mutableData['type'] == 'automation') {
int index = state.thenItems.indexWhere(
(item) => item['deviceId'] == mutableData['deviceId']);
if (index != -1) {
return;
}
final result = await showDialog<bool>(
context: context,
builder: (BuildContext context) => AutomationDialog(
@ -169,9 +178,14 @@ class ThenContainer extends StatelessWidget {
}
if (mutableData['type'] == 'tap_to_run' && state.isAutomation) {
int index = state.thenItems.indexWhere(
(item) => item['deviceId'] == mutableData['deviceId']);
if (index != -1) {
return;
}
context.read<RoutineBloc>().add(AddToThenContainer({
...mutableData,
'imagePath': Assets.logo,
'imagePath': mutableData['imagePath'] ?? Assets.logo,
'title': mutableData['name'],
}));

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/utils/color_manager.dart';
import 'package:syncrow_web/utils/navigation_service.dart';
class CustomSnackBar {
@ -11,6 +12,35 @@ class CustomSnackBar {
}
}
static redSnackBar(String message) {
final key = NavigationService.snackbarKey;
BuildContext? currentContext = key?.currentContext;
if (key != null && currentContext != null) {
final snackBar = SnackBar(
padding: const EdgeInsets.all(16),
backgroundColor: ColorsManager.red,
content: Row(mainAxisAlignment: MainAxisAlignment.center, children: [
const Icon(
Icons.check_circle,
color: ColorsManager.whiteColors,
size: 32,
),
const SizedBox(
width: 8,
),
Text(
message,
style: Theme.of(currentContext)
.textTheme
.bodySmall!
.copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
)
]),
);
key.currentState?.showSnackBar(snackBar);
}
}
static greenSnackBar(String message) {
final key = NavigationService.snackbarKey;
BuildContext? currentContext = key?.currentContext;
@ -29,8 +59,10 @@ class CustomSnackBar {
),
Text(
message,
style: Theme.of(currentContext).textTheme.bodySmall!.copyWith(
fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
style: Theme.of(currentContext)
.textTheme
.bodySmall!
.copyWith(fontSize: 14, fontWeight: FontWeight.w500, color: Colors.green),
)
]),
);