mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-15 09:45:25 +00:00
@ -88,31 +88,32 @@ class DeviceManagementPage extends StatelessWidget with HelperResponsiveLayout {
|
|||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
rightBody: const NavigateHomeGridView(),
|
rightBody: const NavigateHomeGridView(),
|
||||||
scaffoldBody: BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
|
scaffoldBody: CreateNewRoutineView(),
|
||||||
builder: (context, state) {
|
// BlocBuilder<SwitchTabsBloc, SwitchTabsState>(
|
||||||
if (state is SelectedTabState && state.selectedTab) {
|
// builder: (context, state) {
|
||||||
return const RoutinesView();
|
// if (state is SelectedTabState && state.selectedTab) {
|
||||||
}
|
// return const RoutinesView();
|
||||||
if (state is ShowCreateRoutineState && state.showCreateRoutine) {
|
// }
|
||||||
return const CreateNewRoutineView();
|
// if (state is ShowCreateRoutineState && state.showCreateRoutine) {
|
||||||
}
|
// return const CreateNewRoutineView();
|
||||||
|
// }
|
||||||
|
|
||||||
return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
|
// return BlocBuilder<DeviceManagementBloc, DeviceManagementState>(
|
||||||
builder: (context, deviceState) {
|
// builder: (context, deviceState) {
|
||||||
if (deviceState is DeviceManagementLoading) {
|
// if (deviceState is DeviceManagementLoading) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
// return const Center(child: CircularProgressIndicator());
|
||||||
} else if (deviceState is DeviceManagementLoaded ||
|
// } else if (deviceState is DeviceManagementLoaded ||
|
||||||
deviceState is DeviceManagementFiltered) {
|
// deviceState is DeviceManagementFiltered) {
|
||||||
final devices = (deviceState as dynamic).devices ??
|
// final devices = (deviceState as dynamic).devices ??
|
||||||
(deviceState as DeviceManagementFiltered).filteredDevices;
|
// (deviceState as DeviceManagementFiltered).filteredDevices;
|
||||||
|
|
||||||
return DeviceManagementBody(devices: devices);
|
// return DeviceManagementBody(devices: devices);
|
||||||
} else {
|
// } else {
|
||||||
return const Center(child: Text('Error fetching Devices'));
|
// return const Center(child: Text('Error fetching Devices'));
|
||||||
}
|
// }
|
||||||
},
|
// },
|
||||||
);
|
// );
|
||||||
}),
|
// }),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,10 @@ import 'dart:async';
|
|||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart';
|
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_event.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart';
|
import 'package:syncrow_web/pages/routiens/bloc/effective_period/effect_period_state.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
|
||||||
import 'package:syncrow_web/utils/constants/app_enum.dart';
|
import 'package:syncrow_web/utils/constants/app_enum.dart';
|
||||||
|
import 'package:syncrow_web/utils/navigation_service.dart';
|
||||||
|
|
||||||
class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
|
class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
|
||||||
final daysMap = {
|
final daysMap = {
|
||||||
@ -49,9 +52,9 @@ class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// BlocProvider.of<CreateSceneBloc>(NavigationService.navigatorKey.currentContext!).add(
|
BlocProvider.of<RoutineBloc>(NavigationService.navigatorKey.currentContext!).add(
|
||||||
// EffectiveTimePeriodEvent(
|
EffectiveTimePeriodEvent(
|
||||||
// EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary)));
|
EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary)));
|
||||||
|
|
||||||
emit(state.copyWith(
|
emit(state.copyWith(
|
||||||
selectedPeriod: event.period, customStartTime: startTime, customEndTime: endTime));
|
selectedPeriod: event.period, customStartTime: startTime, customEndTime: endTime));
|
||||||
@ -68,11 +71,11 @@ class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
|
|||||||
final newDaysBinary = daysList.join();
|
final newDaysBinary = daysList.join();
|
||||||
emit(state.copyWith(selectedDaysBinary: newDaysBinary));
|
emit(state.copyWith(selectedDaysBinary: newDaysBinary));
|
||||||
|
|
||||||
// BlocProvider.of<CreateSceneBloc>(NavigationService.navigatorKey.currentContext!).add(
|
BlocProvider.of<RoutineBloc>(NavigationService.navigatorKey.currentContext!).add(
|
||||||
// EffectiveTimePeriodEvent(EffectiveTime(
|
EffectiveTimePeriodEvent(EffectiveTime(
|
||||||
// start: state.customStartTime ?? '00:00',
|
start: state.customStartTime ?? '00:00',
|
||||||
// end: state.customEndTime ?? '23:59',
|
end: state.customEndTime ?? '23:59',
|
||||||
// loops: newDaysBinary)));
|
loops: newDaysBinary)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onSetCustomTime(SetCustomTime event, Emitter<EffectPeriodState> emit) {
|
void _onSetCustomTime(SetCustomTime event, Emitter<EffectPeriodState> emit) {
|
||||||
@ -94,9 +97,9 @@ class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
|
|||||||
emit(
|
emit(
|
||||||
state.copyWith(customStartTime: startTime, customEndTime: endTime, selectedPeriod: period));
|
state.copyWith(customStartTime: startTime, customEndTime: endTime, selectedPeriod: period));
|
||||||
|
|
||||||
// BlocProvider.of<CreateSceneBloc>(NavigationService.navigatorKey.currentContext!).add(
|
BlocProvider.of<RoutineBloc>(NavigationService.navigatorKey.currentContext!).add(
|
||||||
// EffectiveTimePeriodEvent(
|
EffectiveTimePeriodEvent(
|
||||||
// EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary)));
|
EffectiveTime(start: startTime, end: endTime, loops: state.selectedDaysBinary)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onResetEffectivePeriod(ResetEffectivePeriod event, Emitter<EffectPeriodState> emit) {
|
void _onResetEffectivePeriod(ResetEffectivePeriod event, Emitter<EffectPeriodState> emit) {
|
||||||
@ -106,8 +109,8 @@ class EffectPeriodBloc extends Bloc<EffectPeriodEvent, EffectPeriodState> {
|
|||||||
customEndTime: '23:59',
|
customEndTime: '23:59',
|
||||||
selectedDaysBinary: '1111111'));
|
selectedDaysBinary: '1111111'));
|
||||||
|
|
||||||
// BlocProvider.of<CreateSceneBloc>(NavigationService.navigatorKey.currentContext!).add(
|
BlocProvider.of<RoutineBloc>(NavigationService.navigatorKey.currentContext!).add(
|
||||||
// EffectiveTimePeriodEvent(EffectiveTime(start: '00:00', end: '23:59', loops: '1111111')));
|
EffectiveTimePeriodEvent(EffectiveTime(start: '00:00', end: '23:59', loops: '1111111')));
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onResetDays(ResetDays event, Emitter<EffectPeriodState> emit) {
|
void _onResetDays(ResetDays event, Emitter<EffectPeriodState> emit) {
|
||||||
|
@ -3,7 +3,8 @@ import 'dart:async';
|
|||||||
import 'package:bloc/bloc.dart';
|
import 'package:bloc/bloc.dart';
|
||||||
import 'package:equatable/equatable.dart';
|
import 'package:equatable/equatable.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/models/create_scene/create_scene_model.dart';
|
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
|
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
|
import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
|
||||||
import 'package:syncrow_web/services/routines_api.dart';
|
import 'package:syncrow_web/services/routines_api.dart';
|
||||||
@ -14,8 +15,8 @@ part 'routine_state.dart';
|
|||||||
const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6';
|
const spaceId = '25c96044-fadf-44bb-93c7-3c079e527ce6';
|
||||||
|
|
||||||
class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
||||||
bool isAutomation = false;
|
// bool isAutomation = false;
|
||||||
bool isTabToRun = false;
|
// bool isTabToRun = false;
|
||||||
|
|
||||||
RoutineBloc() : super(const RoutineState()) {
|
RoutineBloc() : super(const RoutineState()) {
|
||||||
on<AddToIfContainer>(_onAddToIfContainer);
|
on<AddToIfContainer>(_onAddToIfContainer);
|
||||||
@ -27,28 +28,45 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
on<AddSelectedIcon>(_onAddSelectedIcon);
|
on<AddSelectedIcon>(_onAddSelectedIcon);
|
||||||
on<CreateSceneEvent>(_onCreateScene);
|
on<CreateSceneEvent>(_onCreateScene);
|
||||||
on<RemoveDragCard>(_onRemoveDragCard);
|
on<RemoveDragCard>(_onRemoveDragCard);
|
||||||
|
on<ChangeAutomationOperator>(_changeOperatorOperator);
|
||||||
|
on<EffectiveTimePeriodEvent>(_onEffectiveTimeEvent);
|
||||||
|
on<CreateAutomationEvent>(_onCreateAutomation);
|
||||||
|
on<SetRoutineName>(_onSetRoutineName);
|
||||||
// on<RemoveFunction>(_onRemoveFunction);
|
// on<RemoveFunction>(_onRemoveFunction);
|
||||||
// on<ClearFunctions>(_onClearFunctions);
|
// on<ClearFunctions>(_onClearFunctions);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddToIfContainer(AddToIfContainer event, Emitter<RoutineState> emit) {
|
void _onAddToIfContainer(AddToIfContainer event, Emitter<RoutineState> emit) {
|
||||||
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems)..add(event.item);
|
final updatedIfItems = List<Map<String, dynamic>>.from(state.ifItems)
|
||||||
|
..add(event.item);
|
||||||
if (event.isTabToRun) {
|
if (event.isTabToRun) {
|
||||||
isTabToRun = true;
|
emit(state.copyWith(
|
||||||
isAutomation = false;
|
ifItems: updatedIfItems, isTabToRun: true, isAutomation: false));
|
||||||
} else {
|
} else {
|
||||||
isTabToRun = false;
|
emit(state.copyWith(
|
||||||
isAutomation = true;
|
ifItems: updatedIfItems, isTabToRun: false, isAutomation: true));
|
||||||
}
|
}
|
||||||
emit(state.copyWith(ifItems: updatedIfItems));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddToThenContainer(AddToThenContainer event, Emitter<RoutineState> emit) {
|
void _onAddToThenContainer(
|
||||||
final updatedThenItems = List<Map<String, dynamic>>.from(state.thenItems)..add(event.item);
|
AddToThenContainer event, Emitter<RoutineState> emit) {
|
||||||
emit(state.copyWith(thenItems: updatedThenItems));
|
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']);
|
||||||
|
// Replace the map if the index is valid
|
||||||
|
if (index != -1) {
|
||||||
|
currentItems[index] = event.item;
|
||||||
|
} else {
|
||||||
|
currentItems.add(event.item);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _onAddFunctionsToRoutine(AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
emit(state.copyWith(thenItems: currentItems));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onAddFunctionsToRoutine(
|
||||||
|
AddFunctionToRoutine event, Emitter<RoutineState> emit) {
|
||||||
try {
|
try {
|
||||||
if (event.functions.isEmpty) return;
|
if (event.functions.isEmpty) return;
|
||||||
|
|
||||||
@ -57,9 +75,11 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
|
if (currentSelectedFunctions.containsKey(event.uniqueCustomId)) {
|
||||||
currentSelectedFunctions[event.uniqueCustomId] =
|
currentSelectedFunctions[event.uniqueCustomId] =
|
||||||
List.from(currentSelectedFunctions[event.uniqueCustomId]!)..addAll(event.functions);
|
List.from(currentSelectedFunctions[event.uniqueCustomId]!)
|
||||||
|
..addAll(event.functions);
|
||||||
} else {
|
} else {
|
||||||
currentSelectedFunctions[event.uniqueCustomId] = List.from(event.functions);
|
currentSelectedFunctions[event.uniqueCustomId] =
|
||||||
|
List.from(event.functions);
|
||||||
}
|
}
|
||||||
|
|
||||||
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
|
emit(state.copyWith(selectedFunctions: currentSelectedFunctions));
|
||||||
@ -68,7 +88,8 @@ 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));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -87,7 +108,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));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@ -106,13 +128,15 @@ 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));
|
emit(state.copyWith(isLoading: true, errorMessage: null));
|
||||||
await Future.delayed(const Duration(seconds: 1));
|
await Future.delayed(const Duration(seconds: 1));
|
||||||
emit(state.copyWith(searchText: event.query));
|
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));
|
emit(state.copyWith(selectedIcon: event.icon));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -121,7 +145,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
return actions.first['deviceId'] == 'delay';
|
return actions.first['deviceId'] == 'delay';
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _onCreateScene(CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
Future<void> _onCreateScene(
|
||||||
|
CreateSceneEvent event, Emitter<RoutineState> emit) async {
|
||||||
try {
|
try {
|
||||||
// Check if first action is delay
|
// Check if first action is delay
|
||||||
if (_isFirstActionDelay(state.thenItems)) {
|
if (_isFirstActionDelay(state.thenItems)) {
|
||||||
@ -136,7 +161,8 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
|
|
||||||
final actions = state.thenItems
|
final actions = state.thenItems
|
||||||
.map((item) {
|
.map((item) {
|
||||||
final functions = state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
final functions =
|
||||||
|
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||||
if (functions.isEmpty) return null;
|
if (functions.isEmpty) return null;
|
||||||
|
|
||||||
final function = functions.first;
|
final function = functions.first;
|
||||||
@ -191,9 +217,119 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FutureOr<void> _onRemoveDragCard(RemoveDragCard event, Emitter<RoutineState> emit) {
|
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',
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
emit(state.copyWith(isLoading: true));
|
||||||
|
|
||||||
|
final conditions = state.ifItems
|
||||||
|
.map((item) {
|
||||||
|
final functions =
|
||||||
|
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||||
|
if (functions.isEmpty) return null;
|
||||||
|
|
||||||
|
final function = functions.first;
|
||||||
|
return CreateCondition(
|
||||||
|
code: state.ifItems.indexOf(item) + 1,
|
||||||
|
entityId: function.entityId,
|
||||||
|
entityType: 'device_report',
|
||||||
|
expr: ConditionExpr(
|
||||||
|
statusCode: function.functionCode,
|
||||||
|
comparator: function.condition ?? '==',
|
||||||
|
statusValue: function.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.whereType<CreateCondition>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
if (conditions.isEmpty) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
errorMessage: 'At least one condition is required',
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final createAutomationModel = CreateAutomationModel(
|
||||||
|
unitUuid: spaceId,
|
||||||
|
automationName: state.routineName!,
|
||||||
|
decisionExpr: state.selectedAutomationOperator,
|
||||||
|
effectiveTime: state.effectiveTime ??
|
||||||
|
EffectiveTime(
|
||||||
|
start: '00:00',
|
||||||
|
end: '23:59',
|
||||||
|
loops: '1111111',
|
||||||
|
),
|
||||||
|
conditions: conditions,
|
||||||
|
actions: state.thenItems
|
||||||
|
.map((item) {
|
||||||
|
final functions =
|
||||||
|
state.selectedFunctions[item['uniqueCustomId']] ?? [];
|
||||||
|
if (functions.isEmpty) return null;
|
||||||
|
|
||||||
|
final function = functions.first;
|
||||||
|
if (function.functionCode == 'automation') {
|
||||||
|
return CreateSceneAction(
|
||||||
|
entityId: function.entityId,
|
||||||
|
actionExecutor: function.value,
|
||||||
|
executorProperty: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (item['deviceId'] == 'delay') {
|
||||||
|
return CreateSceneAction(
|
||||||
|
entityId: function.entityId,
|
||||||
|
actionExecutor: 'delay',
|
||||||
|
executorProperty: CreateSceneExecutorProperty(
|
||||||
|
functionCode: '',
|
||||||
|
functionValue: '',
|
||||||
|
delaySeconds: function.value,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return CreateSceneAction(
|
||||||
|
entityId: function.entityId,
|
||||||
|
actionExecutor: 'device_issue',
|
||||||
|
executorProperty: CreateSceneExecutorProperty(
|
||||||
|
functionCode: function.functionCode,
|
||||||
|
functionValue: function.value,
|
||||||
|
delaySeconds: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.whereType<CreateSceneAction>()
|
||||||
|
.toList(),
|
||||||
|
);
|
||||||
|
|
||||||
|
final result = await SceneApi.createAutomation(createAutomationModel);
|
||||||
|
if (result['success']) {
|
||||||
|
emit(const RoutineState());
|
||||||
|
} else {
|
||||||
|
emit(state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
errorMessage: result['message'],
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
isLoading: false,
|
||||||
|
errorMessage: e.toString(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onRemoveDragCard(
|
||||||
|
RemoveDragCard event, Emitter<RoutineState> emit) {
|
||||||
if (event.isFromThen) {
|
if (event.isFromThen) {
|
||||||
/// remove element from thenItems at specific index
|
|
||||||
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
final thenItems = List<Map<String, dynamic>>.from(state.thenItems);
|
||||||
thenItems.removeAt(event.index);
|
thenItems.removeAt(event.index);
|
||||||
emit(state.copyWith(thenItems: thenItems));
|
emit(state.copyWith(thenItems: thenItems));
|
||||||
@ -203,4 +339,21 @@ class RoutineBloc extends Bloc<RoutineEvent, RoutineState> {
|
|||||||
emit(state.copyWith(ifItems: ifItems));
|
emit(state.copyWith(ifItems: ifItems));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _changeOperatorOperator(
|
||||||
|
ChangeAutomationOperator event, Emitter<RoutineState> emit) {
|
||||||
|
emit(state.copyWith(
|
||||||
|
selectedAutomationOperator: event.operator,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onEffectiveTimeEvent(
|
||||||
|
EffectiveTimePeriodEvent event, Emitter<RoutineState> emit) {
|
||||||
|
emit(state.copyWith(effectiveTime: event.effectiveTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
FutureOr<void> _onSetRoutineName(
|
||||||
|
SetRoutineName event, Emitter<RoutineState> emit) {
|
||||||
|
emit(state.copyWith(routineName: event.name));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -87,4 +87,38 @@ class RemoveDragCard extends RoutineEvent {
|
|||||||
List<Object> get props => [index];
|
List<Object> get props => [index];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class ChangeAutomationOperator extends RoutineEvent {
|
||||||
|
final String operator;
|
||||||
|
const ChangeAutomationOperator({required this.operator});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [operator];
|
||||||
|
}
|
||||||
|
|
||||||
|
class EffectiveTimePeriodEvent extends RoutineEvent {
|
||||||
|
final EffectiveTime effectiveTime;
|
||||||
|
const EffectiveTimePeriodEvent(this.effectiveTime);
|
||||||
|
@override
|
||||||
|
List<Object> get props => [effectiveTime];
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateAutomationEvent extends RoutineEvent {
|
||||||
|
// final CreateAutomationModel createAutomationModel;
|
||||||
|
final String? automationId;
|
||||||
|
final bool updateAutomation;
|
||||||
|
|
||||||
|
const CreateAutomationEvent({
|
||||||
|
//required this.createAutomationModel,
|
||||||
|
this.automationId,
|
||||||
|
this.updateAutomation = false,
|
||||||
|
});
|
||||||
|
@override
|
||||||
|
List<Object> get props => [];
|
||||||
|
}
|
||||||
|
|
||||||
|
class SetRoutineName extends RoutineEvent {
|
||||||
|
final String name;
|
||||||
|
const SetRoutineName(this.name);
|
||||||
|
@override
|
||||||
|
List<Object> get props => [name];
|
||||||
|
}
|
||||||
class ClearFunctions extends RoutineEvent {}
|
class ClearFunctions extends RoutineEvent {}
|
||||||
|
@ -14,9 +14,13 @@ class RoutineState extends Equatable {
|
|||||||
final String? routineName;
|
final String? routineName;
|
||||||
final String? selectedIcon;
|
final String? selectedIcon;
|
||||||
final String? searchText;
|
final String? searchText;
|
||||||
|
final bool isTabToRun;
|
||||||
|
final bool isAutomation;
|
||||||
|
final String selectedAutomationOperator;
|
||||||
|
final EffectiveTime? effectiveTime;
|
||||||
|
|
||||||
const RoutineState(
|
const RoutineState({
|
||||||
{this.ifItems = const [],
|
this.ifItems = const [],
|
||||||
this.thenItems = const [],
|
this.thenItems = const [],
|
||||||
this.availableCards = const [],
|
this.availableCards = const [],
|
||||||
this.scenes = const [],
|
this.scenes = const [],
|
||||||
@ -28,10 +32,15 @@ class RoutineState extends Equatable {
|
|||||||
this.selectedIcon,
|
this.selectedIcon,
|
||||||
this.loadScenesErrorMessage,
|
this.loadScenesErrorMessage,
|
||||||
this.loadAutomationErrorMessage,
|
this.loadAutomationErrorMessage,
|
||||||
this.searchText});
|
this.searchText,
|
||||||
|
this.isTabToRun = false,
|
||||||
|
this.isAutomation = false,
|
||||||
|
this.selectedAutomationOperator = 'or',
|
||||||
|
this.effectiveTime,
|
||||||
|
});
|
||||||
|
|
||||||
RoutineState copyWith(
|
RoutineState copyWith({
|
||||||
{List<Map<String, dynamic>>? ifItems,
|
List<Map<String, dynamic>>? ifItems,
|
||||||
List<Map<String, dynamic>>? thenItems,
|
List<Map<String, dynamic>>? thenItems,
|
||||||
List<ScenesModel>? scenes,
|
List<ScenesModel>? scenes,
|
||||||
List<ScenesModel>? automations,
|
List<ScenesModel>? automations,
|
||||||
@ -42,7 +51,12 @@ class RoutineState extends Equatable {
|
|||||||
String? selectedIcon,
|
String? selectedIcon,
|
||||||
String? loadAutomationErrorMessage,
|
String? loadAutomationErrorMessage,
|
||||||
String? loadScenesErrorMessage,
|
String? loadScenesErrorMessage,
|
||||||
String? searchText}) {
|
String? searchText,
|
||||||
|
bool? isTabToRun,
|
||||||
|
bool? isAutomation,
|
||||||
|
String? selectedAutomationOperator,
|
||||||
|
EffectiveTime? effectiveTime,
|
||||||
|
}) {
|
||||||
return RoutineState(
|
return RoutineState(
|
||||||
ifItems: ifItems ?? this.ifItems,
|
ifItems: ifItems ?? this.ifItems,
|
||||||
thenItems: thenItems ?? this.thenItems,
|
thenItems: thenItems ?? this.thenItems,
|
||||||
@ -53,9 +67,17 @@ class RoutineState extends Equatable {
|
|||||||
errorMessage: errorMessage ?? this.errorMessage,
|
errorMessage: errorMessage ?? this.errorMessage,
|
||||||
routineName: routineName ?? this.routineName,
|
routineName: routineName ?? this.routineName,
|
||||||
selectedIcon: selectedIcon ?? this.selectedIcon,
|
selectedIcon: selectedIcon ?? this.selectedIcon,
|
||||||
loadScenesErrorMessage: loadScenesErrorMessage ?? this.loadScenesErrorMessage,
|
loadScenesErrorMessage:
|
||||||
loadAutomationErrorMessage: loadAutomationErrorMessage ?? this.loadAutomationErrorMessage,
|
loadScenesErrorMessage ?? this.loadScenesErrorMessage,
|
||||||
searchText: searchText ?? this.searchText);
|
loadAutomationErrorMessage:
|
||||||
|
loadAutomationErrorMessage ?? this.loadAutomationErrorMessage,
|
||||||
|
searchText: searchText ?? this.searchText,
|
||||||
|
isTabToRun: isTabToRun ?? this.isTabToRun,
|
||||||
|
isAutomation: isAutomation ?? this.isAutomation,
|
||||||
|
selectedAutomationOperator:
|
||||||
|
selectedAutomationOperator ?? this.selectedAutomationOperator,
|
||||||
|
effectiveTime: effectiveTime ?? this.effectiveTime,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -71,6 +93,10 @@ class RoutineState extends Equatable {
|
|||||||
selectedIcon,
|
selectedIcon,
|
||||||
loadScenesErrorMessage,
|
loadScenesErrorMessage,
|
||||||
loadAutomationErrorMessage,
|
loadAutomationErrorMessage,
|
||||||
searchText
|
searchText,
|
||||||
|
isTabToRun,
|
||||||
|
isAutomation,
|
||||||
|
selectedAutomationOperator,
|
||||||
|
effectiveTime
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -43,7 +43,7 @@ class SaveRoutineHelper {
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
if (context.read<RoutineBloc>().isTabToRun)
|
if (state.isTabToRun)
|
||||||
ListTile(
|
ListTile(
|
||||||
leading: SvgPicture.asset(
|
leading: SvgPicture.asset(
|
||||||
Assets.tabToRun,
|
Assets.tabToRun,
|
||||||
@ -52,6 +52,35 @@ class SaveRoutineHelper {
|
|||||||
),
|
),
|
||||||
title: const Text('Tab to run'),
|
title: const Text('Tab to run'),
|
||||||
),
|
),
|
||||||
|
if (state.isAutomation)
|
||||||
|
...state.ifItems.map((item) {
|
||||||
|
final functions = state.selectedFunctions[
|
||||||
|
item['uniqueCustomId']] ??
|
||||||
|
[];
|
||||||
|
return ListTile(
|
||||||
|
leading: SvgPicture.asset(
|
||||||
|
item['imagePath'],
|
||||||
|
width: 22,
|
||||||
|
height: 22,
|
||||||
|
),
|
||||||
|
title: Text(item['title'],
|
||||||
|
style: const TextStyle(fontSize: 14)),
|
||||||
|
subtitle: Wrap(
|
||||||
|
children: functions
|
||||||
|
.map((f) => Text(
|
||||||
|
'${f.operationName}: ${f.value}, ',
|
||||||
|
style: const TextStyle(
|
||||||
|
color: ColorsManager
|
||||||
|
.grayColor,
|
||||||
|
fontSize: 8),
|
||||||
|
overflow:
|
||||||
|
TextOverflow.ellipsis,
|
||||||
|
maxLines: 3,
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -0,0 +1,173 @@
|
|||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart';
|
||||||
|
|
||||||
|
class CreateAutomationModel {
|
||||||
|
String unitUuid;
|
||||||
|
String automationName;
|
||||||
|
String decisionExpr;
|
||||||
|
EffectiveTime effectiveTime;
|
||||||
|
List<CreateCondition> conditions;
|
||||||
|
List<CreateSceneAction> actions;
|
||||||
|
|
||||||
|
CreateAutomationModel({
|
||||||
|
required this.unitUuid,
|
||||||
|
required this.automationName,
|
||||||
|
required this.decisionExpr,
|
||||||
|
required this.effectiveTime,
|
||||||
|
required this.conditions,
|
||||||
|
required this.actions,
|
||||||
|
});
|
||||||
|
|
||||||
|
CreateAutomationModel copyWith({
|
||||||
|
String? unitUuid,
|
||||||
|
String? automationName,
|
||||||
|
String? decisionExpr,
|
||||||
|
EffectiveTime? effectiveTime,
|
||||||
|
List<CreateCondition>? conditions,
|
||||||
|
List<CreateSceneAction>? actions,
|
||||||
|
}) {
|
||||||
|
return CreateAutomationModel(
|
||||||
|
unitUuid: unitUuid ?? this.unitUuid,
|
||||||
|
automationName: automationName ?? this.automationName,
|
||||||
|
decisionExpr: decisionExpr ?? this.decisionExpr,
|
||||||
|
effectiveTime: effectiveTime ?? this.effectiveTime,
|
||||||
|
conditions: conditions ?? this.conditions,
|
||||||
|
actions: actions ?? this.actions,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap([String? automationId]) {
|
||||||
|
return {
|
||||||
|
if (automationId == null) 'spaceUuid': unitUuid,
|
||||||
|
'automationName': automationName,
|
||||||
|
'decisionExpr': decisionExpr,
|
||||||
|
'effectiveTime': effectiveTime.toMap(),
|
||||||
|
'conditions': conditions.map((x) => x.toMap()).toList(),
|
||||||
|
'actions': actions.map((x) => x.toMap()).toList(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CreateAutomationModel.fromMap(Map<String, dynamic> map) {
|
||||||
|
return CreateAutomationModel(
|
||||||
|
unitUuid: map['spaceUuid'] ?? '',
|
||||||
|
automationName: map['automationName'] ?? '',
|
||||||
|
decisionExpr: map['decisionExpr'] ?? '',
|
||||||
|
effectiveTime: EffectiveTime.fromMap(map['effectiveTime']),
|
||||||
|
conditions: List<CreateCondition>.from(
|
||||||
|
map['conditions']?.map((x) => CreateCondition.fromMap(x))),
|
||||||
|
actions: List<CreateSceneAction>.from(
|
||||||
|
map['actions']?.map((x) => CreateSceneAction.fromMap(x))),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
String toJson([String? automationId]) => json.encode(toMap(automationId));
|
||||||
|
|
||||||
|
factory CreateAutomationModel.fromJson(String source) =>
|
||||||
|
CreateAutomationModel.fromMap(json.decode(source));
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() {
|
||||||
|
return 'CreateAutomationModel(unitUuid: $unitUuid, automationName: $automationName, decisionExpr: $decisionExpr, effectiveTime: $effectiveTime, conditions: $conditions, actions: $actions)';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class EffectiveTime {
|
||||||
|
String start;
|
||||||
|
String end;
|
||||||
|
String loops;
|
||||||
|
|
||||||
|
EffectiveTime({
|
||||||
|
required this.start,
|
||||||
|
required this.end,
|
||||||
|
required this.loops,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'start': start,
|
||||||
|
'end': end,
|
||||||
|
'loops': loops,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory EffectiveTime.fromMap(Map<String, dynamic> map) {
|
||||||
|
return EffectiveTime(
|
||||||
|
start: map['start'] ?? '',
|
||||||
|
end: map['end'] ?? '',
|
||||||
|
loops: map['loops'] ?? '',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'EffectiveTime(start: $start, end: $end, loops: $loops)';
|
||||||
|
}
|
||||||
|
|
||||||
|
class CreateCondition {
|
||||||
|
int code;
|
||||||
|
String entityId;
|
||||||
|
String entityType;
|
||||||
|
ConditionExpr expr;
|
||||||
|
|
||||||
|
CreateCondition({
|
||||||
|
required this.code,
|
||||||
|
required this.entityId,
|
||||||
|
required this.entityType,
|
||||||
|
required this.expr,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'code': code,
|
||||||
|
'entityId': entityId,
|
||||||
|
'entityType': entityType,
|
||||||
|
'expr': expr.toMap(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory CreateCondition.fromMap(Map<String, dynamic> map) {
|
||||||
|
return CreateCondition(
|
||||||
|
code: map['code'] ?? 0,
|
||||||
|
entityId: map['entityId'] ?? '',
|
||||||
|
entityType: map['entityType'] ?? '',
|
||||||
|
expr: ConditionExpr.fromMap(map['expr']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'CreateCondition(code: $code, entityId: $entityId, entityType: $entityType, expr: $expr)';
|
||||||
|
}
|
||||||
|
|
||||||
|
class ConditionExpr {
|
||||||
|
String statusCode;
|
||||||
|
String comparator;
|
||||||
|
dynamic statusValue;
|
||||||
|
|
||||||
|
ConditionExpr({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.comparator,
|
||||||
|
required this.statusValue,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toMap() {
|
||||||
|
return {
|
||||||
|
'statusCode': statusCode,
|
||||||
|
'comparator': comparator,
|
||||||
|
'statusValue': statusValue,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
factory ConditionExpr.fromMap(Map<String, dynamic> map) {
|
||||||
|
return ConditionExpr(
|
||||||
|
statusCode: map['statusCode'] ?? '',
|
||||||
|
comparator: map['comparator'] ?? '',
|
||||||
|
statusValue: map['statusValue'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() =>
|
||||||
|
'ConditionExpr(statusCode: $statusCode, comparator: $comparator, statusValue: $statusValue)';
|
||||||
|
}
|
@ -5,12 +5,14 @@ class DialogFooter extends StatelessWidget {
|
|||||||
final VoidCallback onCancel;
|
final VoidCallback onCancel;
|
||||||
final VoidCallback? onConfirm;
|
final VoidCallback? onConfirm;
|
||||||
final bool isConfirmEnabled;
|
final bool isConfirmEnabled;
|
||||||
|
final int? dialogWidth;
|
||||||
|
|
||||||
const DialogFooter({
|
const DialogFooter({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.onCancel,
|
required this.onCancel,
|
||||||
required this.onConfirm,
|
required this.onConfirm,
|
||||||
required this.isConfirmEnabled,
|
required this.isConfirmEnabled,
|
||||||
|
this.dialogWidth,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -26,24 +28,23 @@ class DialogFooter extends StatelessWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
|
||||||
children: [
|
children: [
|
||||||
_buildFooterButton(
|
Expanded(
|
||||||
|
child: _buildFooterButton(
|
||||||
context,
|
context,
|
||||||
'Cancel',
|
'Cancel',
|
||||||
onCancel,
|
onCancel,
|
||||||
width: isConfirmEnabled ? 299 : 179,
|
|
||||||
),
|
),
|
||||||
if (isConfirmEnabled)
|
),
|
||||||
Row(
|
if (isConfirmEnabled) ...[
|
||||||
children: [
|
|
||||||
Container(width: 1, height: 50, color: ColorsManager.greyColor),
|
Container(width: 1, height: 50, color: ColorsManager.greyColor),
|
||||||
_buildFooterButton(
|
Expanded(
|
||||||
|
child: _buildFooterButton(
|
||||||
context,
|
context,
|
||||||
'Confirm',
|
'Confirm',
|
||||||
onConfirm,
|
onConfirm,
|
||||||
width: 299,
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -52,14 +53,12 @@ class DialogFooter extends StatelessWidget {
|
|||||||
Widget _buildFooterButton(
|
Widget _buildFooterButton(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
String text,
|
String text,
|
||||||
VoidCallback? onTap, {
|
VoidCallback? onTap,
|
||||||
required double width,
|
) {
|
||||||
}) {
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: onTap,
|
onTap: onTap,
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
height: 50,
|
height: 50,
|
||||||
width: width,
|
|
||||||
child: Center(
|
child: Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
text,
|
text,
|
||||||
|
@ -3,7 +3,9 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
|||||||
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
|
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart';
|
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
|
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
|
||||||
|
import 'package:syncrow_web/utils/color_manager.dart';
|
||||||
import 'package:syncrow_web/utils/constants/assets.dart';
|
import 'package:syncrow_web/utils/constants/assets.dart';
|
||||||
|
import 'package:syncrow_web/utils/extension/build_context_x.dart';
|
||||||
import 'package:uuid/uuid.dart';
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
class IfContainer extends StatelessWidget {
|
class IfContainer extends StatelessWidget {
|
||||||
@ -20,12 +22,20 @@ class IfContainer extends StatelessWidget {
|
|||||||
padding: const EdgeInsets.all(16),
|
padding: const EdgeInsets.all(16),
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
const Text('IF',
|
const Text('IF',
|
||||||
style:
|
style: TextStyle(
|
||||||
TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
|
fontSize: 18, fontWeight: FontWeight.bold)),
|
||||||
|
if (state.isAutomation)
|
||||||
|
AutomationOperatorSelector(
|
||||||
|
selectedOperator: state.selectedAutomationOperator),
|
||||||
|
],
|
||||||
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
if (context.read<RoutineBloc>().isTabToRun)
|
if (state.isTabToRun)
|
||||||
const Row(
|
const Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
@ -36,7 +46,7 @@ class IfContainer extends StatelessWidget {
|
|||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
if (!context.read<RoutineBloc>().isTabToRun)
|
if (!state.isTabToRun)
|
||||||
Wrap(
|
Wrap(
|
||||||
spacing: 8,
|
spacing: 8,
|
||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
@ -64,35 +74,12 @@ class IfContainer extends StatelessWidget {
|
|||||||
},
|
},
|
||||||
onWillAccept: (data) => data != null,
|
onWillAccept: (data) => data != null,
|
||||||
onAccept: (data) async {
|
onAccept: (data) async {
|
||||||
// final uniqueCustomId = const Uuid().v4();
|
|
||||||
|
|
||||||
// final mutableData = Map<String, dynamic>.from(data);
|
|
||||||
// mutableData['uniqueCustomId'] = uniqueCustomId;
|
|
||||||
|
|
||||||
// if (!context.read<RoutineBloc>().isTabToRun) {
|
|
||||||
// if (data['deviceId'] == 'tab_to_run') {
|
|
||||||
// context.read<RoutineBloc>().add(AddToIfContainer(data, true));
|
|
||||||
// } else {
|
|
||||||
// final result =
|
|
||||||
// await DeviceDialogHelper.showDeviceDialog(context, mutableData);
|
|
||||||
// if (result != null) {
|
|
||||||
// context
|
|
||||||
// .read<RoutineBloc>()
|
|
||||||
// .add(AddToIfContainer(mutableData, false));
|
|
||||||
// } else if (!['AC', '1G', '2G', '3G']
|
|
||||||
// .contains(data['productType'])) {
|
|
||||||
// context
|
|
||||||
// .read<RoutineBloc>()
|
|
||||||
// .add(AddToIfContainer(mutableData, false));
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//}
|
|
||||||
final uniqueCustomId = const Uuid().v4();
|
final uniqueCustomId = const Uuid().v4();
|
||||||
|
|
||||||
final mutableData = Map<String, dynamic>.from(data);
|
final mutableData = Map<String, dynamic>.from(data);
|
||||||
mutableData['uniqueCustomId'] = uniqueCustomId;
|
mutableData['uniqueCustomId'] = uniqueCustomId;
|
||||||
|
|
||||||
if (!context.read<RoutineBloc>().isTabToRun) {
|
if (!state.isTabToRun) {
|
||||||
if (mutableData['deviceId'] == 'tab_to_run') {
|
if (mutableData['deviceId'] == 'tab_to_run') {
|
||||||
context
|
context
|
||||||
.read<RoutineBloc>()
|
.read<RoutineBloc>()
|
||||||
@ -119,3 +106,77 @@ class IfContainer extends StatelessWidget {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AutomationOperatorSelector extends StatelessWidget {
|
||||||
|
const AutomationOperatorSelector({
|
||||||
|
super.key,
|
||||||
|
required this.selectedOperator,
|
||||||
|
});
|
||||||
|
|
||||||
|
final String selectedOperator;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border.all(color: ColorsManager.dividerColor),
|
||||||
|
borderRadius: BorderRadius.circular(8),
|
||||||
|
),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: selectedOperator == 'or'
|
||||||
|
? ColorsManager.dialogBlueTitle
|
||||||
|
: ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'Any condition is met',
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: selectedOperator == 'or'
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<RoutineBloc>()
|
||||||
|
.add(const ChangeAutomationOperator(operator: 'or'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 3,
|
||||||
|
height: 24,
|
||||||
|
color: ColorsManager.dividerColor,
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
style: TextButton.styleFrom(
|
||||||
|
backgroundColor: selectedOperator == 'and'
|
||||||
|
? ColorsManager.dialogBlueTitle
|
||||||
|
: ColorsManager.whiteColors,
|
||||||
|
shape: RoundedRectangleBorder(
|
||||||
|
borderRadius: BorderRadius.circular(0),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
child: Text(
|
||||||
|
'All condition is met',
|
||||||
|
style: context.textTheme.bodyMedium?.copyWith(
|
||||||
|
color: selectedOperator == 'and'
|
||||||
|
? ColorsManager.whiteColors
|
||||||
|
: ColorsManager.blackColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
onPressed: () {
|
||||||
|
context
|
||||||
|
.read<RoutineBloc>()
|
||||||
|
.add(const ChangeAutomationOperator(operator: 'and'));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -0,0 +1,95 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import 'package:flutter_svg/flutter_svg.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/models/device_functions.dart';
|
||||||
|
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 {
|
||||||
|
final String automationName;
|
||||||
|
final String automationId;
|
||||||
|
final String uniqueCustomId;
|
||||||
|
|
||||||
|
const AutomationDialog({
|
||||||
|
super.key,
|
||||||
|
required this.automationName,
|
||||||
|
required this.automationId,
|
||||||
|
required this.uniqueCustomId,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
_AutomationDialogState createState() => _AutomationDialogState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AutomationDialogState extends State<AutomationDialog> {
|
||||||
|
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<bool>(
|
||||||
|
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<bool>(
|
||||||
|
value: false,
|
||||||
|
groupValue: _isEnabled,
|
||||||
|
onChanged: (bool? value) {
|
||||||
|
setState(() {
|
||||||
|
_isEnabled = value!;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 16),
|
||||||
|
DialogFooter(
|
||||||
|
onConfirm: () {
|
||||||
|
context.read<RoutineBloc>().add(
|
||||||
|
AddFunctionToRoutine(
|
||||||
|
[
|
||||||
|
DeviceFunctionData(
|
||||||
|
entityId: widget.automationId,
|
||||||
|
functionCode: 'automation',
|
||||||
|
value: _isEnabled,
|
||||||
|
operationName: 'Automation',
|
||||||
|
),
|
||||||
|
],
|
||||||
|
widget.uniqueCustomId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
onCancel: () => Navigator.of(context).pop(false),
|
||||||
|
isConfirmEnabled: true,
|
||||||
|
dialogWidth: 400,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -8,13 +8,25 @@ import 'package:syncrow_web/pages/routiens/widgets/dialog_header.dart';
|
|||||||
|
|
||||||
class DelayHelper {
|
class DelayHelper {
|
||||||
static Future<Map<String, dynamic>?> showDelayPickerDialog(
|
static Future<Map<String, dynamic>?> showDelayPickerDialog(
|
||||||
BuildContext context, String uniqueCustomId) async {
|
BuildContext context, Map<String, dynamic> data) async {
|
||||||
int hours = 0;
|
int hours = 0;
|
||||||
int minutes = 0;
|
int minutes = 0;
|
||||||
|
|
||||||
return showDialog<Map<String, dynamic>?>(
|
return showDialog<Map<String, dynamic>?>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
|
final routineBloc = context.read<RoutineBloc>();
|
||||||
|
int totalSec = 0;
|
||||||
|
|
||||||
|
final selectedFunctionData =
|
||||||
|
routineBloc.state.selectedFunctions[data['uniqueCustomId']] ?? [];
|
||||||
|
|
||||||
|
if (selectedFunctionData.isNotEmpty) {
|
||||||
|
totalSec = selectedFunctionData[0].value;
|
||||||
|
// Convert seconds to hours and minutes
|
||||||
|
hours = totalSec ~/ 3600;
|
||||||
|
minutes = (totalSec % 3600) ~/ 60;
|
||||||
|
}
|
||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
contentPadding: EdgeInsets.zero,
|
contentPadding: EdgeInsets.zero,
|
||||||
content: Container(
|
content: Container(
|
||||||
@ -31,8 +43,7 @@ class DelayHelper {
|
|||||||
Expanded(
|
Expanded(
|
||||||
child: CupertinoTimerPicker(
|
child: CupertinoTimerPicker(
|
||||||
mode: CupertinoTimerPickerMode.hm,
|
mode: CupertinoTimerPickerMode.hm,
|
||||||
initialTimerDuration:
|
initialTimerDuration: Duration(hours: hours, minutes: minutes),
|
||||||
Duration(hours: hours, minutes: minutes),
|
|
||||||
onTimerDurationChanged: (Duration newDuration) {
|
onTimerDurationChanged: (Duration newDuration) {
|
||||||
hours = newDuration.inHours;
|
hours = newDuration.inHours;
|
||||||
minutes = newDuration.inMinutes % 60;
|
minutes = newDuration.inMinutes % 60;
|
||||||
@ -54,7 +65,7 @@ class DelayHelper {
|
|||||||
value: totalSeconds,
|
value: totalSeconds,
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
uniqueCustomId,
|
data['uniqueCustomId'],
|
||||||
));
|
));
|
||||||
|
|
||||||
Navigator.pop(context, {
|
Navigator.pop(context, {
|
||||||
|
@ -18,7 +18,7 @@ class SettingHelper {
|
|||||||
return showDialog<String>(
|
return showDialog<String>(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (BuildContext context) {
|
builder: (BuildContext context) {
|
||||||
final isAutomation = context.read<RoutineBloc>().isAutomation;
|
final isAutomation = context.read<RoutineBloc>().state.isAutomation;
|
||||||
return BlocProvider(
|
return BlocProvider(
|
||||||
create: (_) =>
|
create: (_) =>
|
||||||
SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? '')),
|
SettingBloc()..add(InitialEvent(selectedIcon: iconId ?? '')),
|
||||||
|
@ -45,7 +45,9 @@ class RoutineSearchAndButtons extends StatelessWidget {
|
|||||||
isRequired: true,
|
isRequired: true,
|
||||||
width: 450,
|
width: 450,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
// context.read<RoutineBloc>().add(SearchRoutines(value));
|
context
|
||||||
|
.read<RoutineBloc>()
|
||||||
|
.add(SetRoutineName(value));
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -34,11 +34,9 @@ class _ScenesAndAutomationsState extends State<ScenesAndAutomations> {
|
|||||||
children: scenes.asMap().entries.map((entry) {
|
children: scenes.asMap().entries.map((entry) {
|
||||||
final scene = entry.value;
|
final scene = entry.value;
|
||||||
if (state.searchText != null && state.searchText!.isNotEmpty) {
|
if (state.searchText != null && state.searchText!.isNotEmpty) {
|
||||||
return scene.name
|
return scene.name.toLowerCase().contains(state.searchText!.toLowerCase())
|
||||||
.toLowerCase()
|
|
||||||
.contains(state.searchText!.toLowerCase())
|
|
||||||
? DraggableCard(
|
? DraggableCard(
|
||||||
imagePath: Assets.logo,
|
imagePath: scene.icon ?? Assets.loginLogo,
|
||||||
title: scene.name,
|
title: scene.name,
|
||||||
deviceData: {
|
deviceData: {
|
||||||
'deviceId': scene.id,
|
'deviceId': scene.id,
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_bloc/flutter_bloc.dart';
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
|
import 'package:syncrow_web/pages/routiens/bloc/routine_bloc/routine_bloc.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/automation_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/delay_dialog.dart';
|
import 'package:syncrow_web/pages/routiens/widgets/routine_dialogs/delay_dialog.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart';
|
import 'package:syncrow_web/pages/routiens/helper/dialog_helper/device_dialog_helper.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
|
import 'package:syncrow_web/pages/routiens/widgets/dragable_card.dart';
|
||||||
@ -34,10 +35,48 @@ class ThenContainer extends StatelessWidget {
|
|||||||
runSpacing: 8,
|
runSpacing: 8,
|
||||||
children: List.generate(
|
children: List.generate(
|
||||||
state.thenItems.length,
|
state.thenItems.length,
|
||||||
(index) => DraggableCard(
|
(index) => GestureDetector(
|
||||||
imagePath:
|
onTap: () async {
|
||||||
state.thenItems[index]['imagePath'] ?? '',
|
if (state.thenItems[index]['deviceId'] ==
|
||||||
title: state.thenItems[index]['title'] ?? '',
|
'delay') {
|
||||||
|
final result = await DelayHelper
|
||||||
|
.showDelayPickerDialog(
|
||||||
|
context, state.thenItems[index]);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
context
|
||||||
|
.read<RoutineBloc>()
|
||||||
|
.add(AddToThenContainer({
|
||||||
|
...state.thenItems[index],
|
||||||
|
'imagePath': Assets.delay,
|
||||||
|
'title': 'Delay',
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final result = await DeviceDialogHelper
|
||||||
|
.showDeviceDialog(
|
||||||
|
context, state.thenItems[index]);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
context.read<RoutineBloc>().add(
|
||||||
|
AddToThenContainer(
|
||||||
|
state.thenItems[index]));
|
||||||
|
} else if (!['AC', '1G', '2G', '3G']
|
||||||
|
.contains(state.thenItems[index]
|
||||||
|
['productType'])) {
|
||||||
|
context.read<RoutineBloc>().add(
|
||||||
|
AddToThenContainer(
|
||||||
|
state.thenItems[index]));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: DraggableCard(
|
||||||
|
imagePath: state.thenItems[index]
|
||||||
|
['imagePath'] ??
|
||||||
|
'',
|
||||||
|
title:
|
||||||
|
state.thenItems[index]['title'] ?? '',
|
||||||
deviceData: state.thenItems[index],
|
deviceData: state.thenItems[index],
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
horizontal: 4, vertical: 8),
|
horizontal: 4, vertical: 8),
|
||||||
@ -48,22 +87,61 @@ class ThenContainer extends StatelessWidget {
|
|||||||
RemoveDragCard(
|
RemoveDragCard(
|
||||||
index: index, isFromThen: true));
|
index: index, isFromThen: true));
|
||||||
},
|
},
|
||||||
|
),
|
||||||
))),
|
))),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
onWillAccept: (data) => data != null,
|
// onWillAcceptWithDetails: (data) {
|
||||||
onAccept: (data) async {
|
// if (data == null) return false;
|
||||||
final uniqueCustomId = const Uuid().v4();
|
// return data.data;
|
||||||
|
|
||||||
final mutableData = Map<String, dynamic>.from(data);
|
// // if (state.isTabToRun) {
|
||||||
|
// // return data.data['type'] == 'automation';
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // if (state.isAutomation) {
|
||||||
|
// // return data.data['type'] == 'scene' ||
|
||||||
|
// // data.data['type'] == 'automation';
|
||||||
|
// // }
|
||||||
|
|
||||||
|
// // return data.data['deviceId'] != null;
|
||||||
|
// },
|
||||||
|
onAcceptWithDetails: (data) async {
|
||||||
|
final uniqueCustomId = const Uuid().v4();
|
||||||
|
final mutableData = Map<String, dynamic>.from(data.data);
|
||||||
mutableData['uniqueCustomId'] = uniqueCustomId;
|
mutableData['uniqueCustomId'] = uniqueCustomId;
|
||||||
|
|
||||||
|
if (mutableData['type'] == 'scene') {
|
||||||
|
context.read<RoutineBloc>().add(AddToThenContainer(mutableData));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mutableData['type'] == 'automation') {
|
||||||
|
final result = await showDialog<bool>(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) => AutomationDialog(
|
||||||
|
automationName: mutableData['name'] ?? 'Automation',
|
||||||
|
automationId: mutableData['deviceId'] ?? '',
|
||||||
|
uniqueCustomId: uniqueCustomId,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result != null) {
|
||||||
|
context.read<RoutineBloc>().add(AddToThenContainer({
|
||||||
|
...mutableData,
|
||||||
|
'imagePath': Assets.automation,
|
||||||
|
'title': mutableData['name'],
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (mutableData['deviceId'] == 'delay') {
|
if (mutableData['deviceId'] == 'delay') {
|
||||||
final result = await DelayHelper.showDelayPickerDialog(
|
final result =
|
||||||
context, mutableData['uniqueCustomId']);
|
await DelayHelper.showDelayPickerDialog(context, mutableData);
|
||||||
|
|
||||||
if (result != null) {
|
if (result != null) {
|
||||||
context.read<RoutineBloc>().add(AddToThenContainer({
|
context.read<RoutineBloc>().add(AddToThenContainer({
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/models/create_scene/create_scene_model.dart';
|
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_automation_model.dart';
|
||||||
|
import 'package:syncrow_web/pages/routiens/models/create_scene_and_autoamtion/create_scene_model.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
|
import 'package:syncrow_web/pages/routiens/models/icon_model.dart';
|
||||||
import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
|
import 'package:syncrow_web/pages/routiens/models/routine_model.dart';
|
||||||
import 'package:syncrow_web/services/api/http_service.dart';
|
import 'package:syncrow_web/services/api/http_service.dart';
|
||||||
@ -29,23 +30,23 @@ class SceneApi {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
//
|
//
|
||||||
// // create automation
|
// create automation
|
||||||
// static Future<Map<String, dynamic>> createAutomation(
|
static Future<Map<String, dynamic>> createAutomation(
|
||||||
// CreateAutomationModel createAutomationModel) async {
|
CreateAutomationModel createAutomationModel) async {
|
||||||
// try {
|
try {
|
||||||
// final response = await _httpService.post(
|
final response = await _httpService.post(
|
||||||
// path: ApiEndpoints.createAutomation,
|
path: ApiEndpoints.createAutomation,
|
||||||
// body: createAutomationModel.toMap(),
|
body: createAutomationModel.toMap(),
|
||||||
// showServerMessage: false,
|
showServerMessage: false,
|
||||||
// expectedResponseModel: (json) {
|
expectedResponseModel: (json) {
|
||||||
// return json;
|
return json;
|
||||||
// },
|
},
|
||||||
// );
|
);
|
||||||
// return response;
|
return response;
|
||||||
// } catch (e) {
|
} catch (e) {
|
||||||
// rethrow;
|
rethrow;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
static Future<List<IconModel>> getIcon() async {
|
static Future<List<IconModel>> getIcon() async {
|
||||||
final response = await _httpService.get(
|
final response = await _httpService.get(
|
||||||
|
@ -54,4 +54,5 @@ abstract class ApiEndpoints {
|
|||||||
static const String getSpaceAutomation = '/automation/{unitUuid}';
|
static const String getSpaceAutomation = '/automation/{unitUuid}';
|
||||||
static const String getIconScene = '/scene/icon';
|
static const String getIconScene = '/scene/icon';
|
||||||
static const String createScene = '/scene/tap-to-run';
|
static const String createScene = '/scene/tap-to-run';
|
||||||
|
static const String createAutomation = '/automation';
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user