push automation details and update automation

This commit is contained in:
ashrafzarkanisala
2024-07-25 00:25:48 +03:00
parent 56024ba3a3
commit 8a4c5af2e7
23 changed files with 1271 additions and 709 deletions

View File

@ -1,6 +1,3 @@
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_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
@ -12,7 +9,6 @@ import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dar
import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/routing_constants.dart'; import 'package:syncrow_app/navigation/routing_constants.dart';
class SceneListview extends StatelessWidget { class SceneListview extends StatelessWidget {
final List<ScenesModel> scenes; final List<ScenesModel> scenes;
final String? loadingSceneId; final String? loadingSceneId;
@ -24,8 +20,7 @@ class SceneListview extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return return ListView.builder(
ListView.builder(
shrinkWrap: true, shrinkWrap: true,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
itemCount: scenes.length, itemCount: scenes.length,
@ -34,7 +29,7 @@ class SceneListview extends StatelessWidget {
final isLoading = loadingSceneId == scene.id; final isLoading = loadingSceneId == scene.id;
return Container( return Container(
padding: const EdgeInsets.only(right: 10), padding: const EdgeInsets.only(right: 10),
child:DefaultContainer( child: DefaultContainer(
onTap: () { onTap: () {
Navigator.pushNamed( Navigator.pushNamed(
context, context,
@ -45,10 +40,11 @@ class SceneListview extends StatelessWidget {
sceneName: scene.name, sceneName: scene.name,
), ),
); );
BlocProvider.of<CreateSceneBloc>(context).add(FetchSceneTasksEvent(sceneId: scene.id)); BlocProvider.of<CreateSceneBloc>(context)
.add(FetchSceneTasksEvent(sceneId: scene.id));
}, },
child:SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width*0.4, width: MediaQuery.of(context).size.width * 0.4,
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
@ -73,8 +69,7 @@ class SceneListview extends StatelessWidget {
], ],
), ),
), ),
) ));
);
}, },
); );
} }

View File

@ -276,8 +276,10 @@ class CreateSceneBloc extends Bloc<CreateSceneEvent, CreateSceneState>
? await SceneApi.updateScene(event.createSceneModel!, event.sceneId) ? await SceneApi.updateScene(event.createSceneModel!, event.sceneId)
: await SceneApi.createScene(event.createSceneModel!); : await SceneApi.createScene(event.createSceneModel!);
} else if (event.createAutomationModel != null) { } else if (event.createAutomationModel != null) {
response = response = event.updateScene
await SceneApi.createAutomation(event.createAutomationModel!); ? await SceneApi.updateAutomation(
event.createAutomationModel!, event.sceneId)
: await SceneApi.createAutomation(event.createAutomationModel!);
} }
if (response['success'] == true) { if (response['success'] == true) {
@ -315,13 +317,31 @@ class CreateSceneBloc extends Bloc<CreateSceneEvent, CreateSceneState>
emit(CreateSceneLoading()); emit(CreateSceneLoading());
try { try {
final response = await SceneApi.getSceneDetails(event.sceneId); final response = event.isAutomation
? await SceneApi.getAutomationDetails(event.sceneId)
: await SceneApi.getSceneDetails(event.sceneId);
if (response.id.isNotEmpty) { if (response.id.isNotEmpty) {
tasksList = List<SceneStaticFunction>.from(getTaskListFunctionsFromApi( if (event.isAutomation) {
actions: response.actions, isAutomation: false)); automationTasksList = List<SceneStaticFunction>.from(
emit(AddSceneTask( getTaskListFunctionsFromApi(
tasksList: tasksList, actions: [],
)); isAutomation: true,
conditions: response.conditions));
tasksList = List<SceneStaticFunction>.from(
getTaskListFunctionsFromApi(
actions: response.actions, isAutomation: false));
emit(AddSceneTask(
automationTasksList: automationTasksList,
tasksList: tasksList,
));
} else {
tasksList = List<SceneStaticFunction>.from(
getTaskListFunctionsFromApi(
actions: response.actions, isAutomation: false));
emit(AddSceneTask(
tasksList: tasksList,
));
}
} else { } else {
emit(const CreateSceneError(message: 'Something went wrong')); emit(const CreateSceneError(message: 'Something went wrong'));
} }
@ -420,7 +440,11 @@ class CreateSceneBloc extends Bloc<CreateSceneEvent, CreateSceneState>
conditionRule = 'and'; conditionRule = 'and';
} }
emit(ConditionSelectedState(event.condition)); emit(AddSceneTask(
tasksList: tasksList,
automationTasksList: automationTasksList,
condition: conditionRule,
));
} }
FutureOr<void> _sceneTypeEvent( FutureOr<void> _sceneTypeEvent(

View File

@ -147,10 +147,13 @@ class ClearTempTaskListEvent extends CreateSceneEvent {
class FetchSceneTasksEvent extends CreateSceneEvent { class FetchSceneTasksEvent extends CreateSceneEvent {
final String sceneId; final String sceneId;
const FetchSceneTasksEvent({required this.sceneId}); final bool isAutomation;
const FetchSceneTasksEvent(
{this.isAutomation = false, required this.sceneId});
@override @override
List<Object> get props => []; List<Object> get props => [sceneId, isAutomation];
} }
class DeleteSceneEvent extends CreateSceneEvent { class DeleteSceneEvent extends CreateSceneEvent {

View File

@ -22,7 +22,9 @@ class CreateSceneError extends CreateSceneState {
class AddSceneTask extends CreateSceneState { class AddSceneTask extends CreateSceneState {
final List<SceneStaticFunction> tasksList; final List<SceneStaticFunction> tasksList;
final List<SceneStaticFunction>? automationTasksList; final List<SceneStaticFunction>? automationTasksList;
const AddSceneTask({required this.tasksList, this.automationTasksList}); final String? condition;
const AddSceneTask(
{required this.tasksList, this.automationTasksList, this.condition});
@override @override
List<Object> get props => [tasksList]; List<Object> get props => [tasksList];

View File

@ -11,16 +11,20 @@ part 'scene_state.dart';
class SceneBloc extends Bloc<SceneEvent, SceneState> { class SceneBloc extends Bloc<SceneEvent, SceneState> {
SceneBloc() : super(SceneInitial()) { SceneBloc() : super(SceneInitial()) {
on<LoadScenes>(_onLoadScenes); on<LoadScenes>(_onLoadScenes);
on<LoadAutomation>(_onLoadAutomation);
on<SceneTrigger>(_onSceneTrigger); on<SceneTrigger>(_onSceneTrigger);
} }
List<ScenesModel> scenes = [];
List<ScenesModel> automationList = [];
Future<void> _onLoadScenes(LoadScenes event, Emitter<SceneState> emit) async { Future<void> _onLoadScenes(LoadScenes event, Emitter<SceneState> emit) async {
emit(SceneLoading()); emit(SceneLoading());
try { try {
if (event.unitId.isNotEmpty) { if (event.unitId.isNotEmpty) {
final scenes = await SceneApi.getScenesByUnitId(event.unitId); scenes = await SceneApi.getScenesByUnitId(event.unitId);
emit(SceneLoaded(scenes)); emit(SceneLoaded(scenes, automationList));
} else { } else {
const SceneError(message: ''); const SceneError(message: '');
} }
@ -29,16 +33,34 @@ class SceneBloc extends Bloc<SceneEvent, SceneState> {
} }
} }
Future<void> _onSceneTrigger(SceneTrigger event, Emitter<SceneState> emit) async { Future<void> _onLoadAutomation(
LoadAutomation event, Emitter<SceneState> emit) async {
emit(SceneLoading());
try {
if (event.unitId.isNotEmpty) {
automationList = await SceneApi.getAutomationByUnitId(event.unitId);
emit(SceneLoaded(scenes, automationList));
} else {
emit(const SceneError(message: 'Unit ID is empty'));
}
} catch (e) {
emit(const SceneError(message: 'Something went wrong'));
}
}
Future<void> _onSceneTrigger(
SceneTrigger event, Emitter<SceneState> emit) async {
final currentState = state; final currentState = state;
if (currentState is SceneLoaded) { if (currentState is SceneLoaded) {
emit(SceneLoaded(currentState.scenes, loadingSceneId: event.sceneId)); emit(SceneLoaded(currentState.scenes, currentState.automationList,
loadingSceneId: event.sceneId));
try { try {
final success = await SceneApi.triggerScene(event.sceneId); final success = await SceneApi.triggerScene(event.sceneId);
if (success) { if (success) {
emit(SceneTriggerSuccess(event.name)); emit(SceneTriggerSuccess(event.name));
emit(SceneLoaded(currentState.scenes)); emit(SceneLoaded(currentState.scenes, currentState.automationList));
} else { } else {
emit(const SceneError(message: 'Something went wrong')); emit(const SceneError(message: 'Something went wrong'));
} }

View File

@ -16,6 +16,15 @@ class LoadScenes extends SceneEvent {
List<Object> get props => [unitId]; List<Object> get props => [unitId];
} }
class LoadAutomation extends SceneEvent {
final String unitId;
const LoadAutomation(this.unitId);
@override
List<Object> get props => [unitId];
}
class SceneTrigger extends SceneEvent { class SceneTrigger extends SceneEvent {
final String sceneId; final String sceneId;
final String name; final String name;

View File

@ -13,12 +13,13 @@ class SceneLoading extends SceneState {}
class SceneLoaded extends SceneState { class SceneLoaded extends SceneState {
final List<ScenesModel> scenes; final List<ScenesModel> scenes;
final List<ScenesModel> automationList;
final String? loadingSceneId; final String? loadingSceneId;
const SceneLoaded(this.scenes, {this.loadingSceneId}); const SceneLoaded(this.scenes, this.automationList, {this.loadingSceneId});
@override @override
List<Object?> get props => [scenes, loadingSceneId]; List<Object?> get props => [scenes, loadingSceneId, automationList];
} }
class SceneError extends SceneState { class SceneError extends SceneState {

View File

@ -100,45 +100,56 @@ mixin SceneLogicHelper {
Navigator.pop(context); Navigator.pop(context);
} }
} else { } else {
// Handle Scene Creation if (isOnlyDelayOrDelayLast(actions)) {
final createSceneModel = CreateSceneModel( Navigator.pop(context);
unitUuid: HomeCubit.getInstance().selectedSpace!.id ?? '', context.showCustomSnackbar(
sceneName: sceneName.text, message: 'A single delay or delay-last operations are NOT allowed.',
decisionExpr: 'and', icon: const Icon(
actions: List.generate( Icons.error,
actions.length, color: Colors.red,
(index) { ),
final task = actions[index]; );
if (task.deviceId == 'delay') { } else {
// Handle Scene Creation
final createSceneModel = CreateSceneModel(
unitUuid: HomeCubit.getInstance().selectedSpace!.id ?? '',
sceneName: sceneName.text,
decisionExpr: 'and',
actions: List.generate(
actions.length,
(index) {
final task = actions[index];
if (task.deviceId == 'delay') {
return CreateSceneAction(
entityId: actions[index].deviceId,
actionExecutor: 'delay',
executorProperty: CreateSceneExecutorProperty(
functionCode: '',
functionValue: '',
delaySeconds: task.functionValue,
),
);
}
return CreateSceneAction( return CreateSceneAction(
entityId: actions[index].deviceId, entityId: task.deviceId,
actionExecutor: 'delay', actionExecutor: 'device_issue',
executorProperty: CreateSceneExecutorProperty( executorProperty: CreateSceneExecutorProperty(
functionCode: '', functionCode: task.code,
functionValue: '', functionValue: task.functionValue,
delaySeconds: task.functionValue, delaySeconds: 0,
), ),
); );
} },
return CreateSceneAction( ),
entityId: task.deviceId, );
actionExecutor: 'device_issue', sceneBloc.add(CreateSceneWithTasksEvent(
executorProperty: CreateSceneExecutorProperty( createSceneModel: createSceneModel,
functionCode: task.code, createAutomationModel: null,
functionValue: task.functionValue, updateScene: updateScene,
delaySeconds: 0, sceneId: sceneId,
), ));
); Navigator.pop(context);
}, }
),
);
sceneBloc.add(CreateSceneWithTasksEvent(
createSceneModel: createSceneModel,
createAutomationModel: null,
updateScene: updateScene,
sceneId: sceneId,
));
Navigator.pop(context);
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -37,9 +37,9 @@ class CreateAutomationModel {
); );
} }
Map<String, dynamic> toMap() { Map<String, dynamic> toMap([String? automationId]) {
return { return {
'unitUuid': unitUuid, if (automationId == null) 'unitUuid': unitUuid,
'automationName': automationName, 'automationName': automationName,
'decisionExpr': decisionExpr, 'decisionExpr': decisionExpr,
'effectiveTime': effectiveTime.toMap(), 'effectiveTime': effectiveTime.toMap(),
@ -61,7 +61,7 @@ class CreateAutomationModel {
); );
} }
String toJson() => json.encode(toMap()); String toJson([String? automationId]) => json.encode(toMap(automationId));
factory CreateAutomationModel.fromJson(String source) => factory CreateAutomationModel.fromJson(String source) =>
CreateAutomationModel.fromMap(json.decode(source)); CreateAutomationModel.fromMap(json.decode(source));

View File

@ -6,6 +6,9 @@ class SceneDetailsModel {
final String status; final String status;
final String type; final String type;
final List<Action> actions; final List<Action> actions;
final List<Condition>? conditions;
final String? decisionExpr;
final EffectiveTime? effectiveTime;
SceneDetailsModel({ SceneDetailsModel({
required this.id, required this.id,
@ -13,6 +16,9 @@ class SceneDetailsModel {
required this.status, required this.status,
required this.type, required this.type,
required this.actions, required this.actions,
this.conditions,
this.decisionExpr,
this.effectiveTime,
}); });
factory SceneDetailsModel.fromRawJson(String str) => factory SceneDetailsModel.fromRawJson(String str) =>
@ -28,6 +34,14 @@ class SceneDetailsModel {
type: json["type"], type: json["type"],
actions: actions:
List<Action>.from(json["actions"].map((x) => Action.fromJson(x))), List<Action>.from(json["actions"].map((x) => Action.fromJson(x))),
conditions: json["conditions"] != null
? List<Condition>.from(
json["conditions"].map((x) => Condition.fromJson(x)))
: null,
decisionExpr: json["decisionExpr"],
effectiveTime: json["effectiveTime"] != null
? EffectiveTime.fromJson(json["effectiveTime"])
: null,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {
@ -36,6 +50,11 @@ class SceneDetailsModel {
"status": status, "status": status,
"type": type, "type": type,
"actions": List<dynamic>.from(actions.map((x) => x.toJson())), "actions": List<dynamic>.from(actions.map((x) => x.toJson())),
"conditions": conditions != null
? List<dynamic>.from(conditions!.map((x) => x.toJson()))
: null,
"decisionExpr": decisionExpr,
"effectiveTime": effectiveTime?.toJson(),
}; };
} }
@ -91,3 +110,93 @@ class ExecutorProperty {
"delaySeconds": delaySeconds, "delaySeconds": delaySeconds,
}; };
} }
class Condition {
final int code;
final String entityId;
final String entityType;
final Expr expr;
Condition({
required this.code,
required this.entityId,
required this.entityType,
required this.expr,
});
factory Condition.fromRawJson(String str) =>
Condition.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Condition.fromJson(Map<String, dynamic> json) => Condition(
code: json["code"],
entityId: json["entityId"],
entityType: json["entityType"],
expr: Expr.fromJson(json["expr"]),
);
Map<String, dynamic> toJson() => {
"code": code,
"entityId": entityId,
"entityType": entityType,
"expr": expr.toJson(),
};
}
class Expr {
final String comparator;
final String statusCode;
final dynamic statusValue;
Expr({
required this.comparator,
required this.statusCode,
required this.statusValue,
});
factory Expr.fromRawJson(String str) => Expr.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Expr.fromJson(Map<String, dynamic> json) => Expr(
comparator: json["comparator"],
statusCode: json["statusCode"],
statusValue: json["statusValue"],
);
Map<String, dynamic> toJson() => {
"comparator": comparator,
"statusCode": statusCode,
"statusValue": statusValue,
};
}
class EffectiveTime {
final String start;
final String end;
final String loops;
EffectiveTime({
required this.start,
required this.end,
required this.loops,
});
factory EffectiveTime.fromRawJson(String str) =>
EffectiveTime.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory EffectiveTime.fromJson(Map<String, dynamic> json) => EffectiveTime(
start: json["start"],
end: json["end"],
loops: json["loops"],
);
Map<String, dynamic> toJson() => {
"start": start,
"end": end,
"loops": loops,
};
}

View File

@ -37,9 +37,10 @@ enum Status { ENABLE }
final statusValues = EnumValues({"enable": Status.ENABLE}); final statusValues = EnumValues({"enable": Status.ENABLE});
enum Type { TAP_TO_RUN } enum Type { TAP_TO_RUN, automation }
final typeValues = EnumValues({"tap_to_run": Type.TAP_TO_RUN}); final typeValues =
EnumValues({"tap_to_run": Type.TAP_TO_RUN, "automation": Type.automation});
class EnumValues<T> { class EnumValues<T> {
Map<String, T> map; Map<String, T> map;

View File

@ -20,7 +20,7 @@ import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class DeviceFunctionsView extends StatelessWidget class DeviceFunctionsView extends StatelessWidget
with SceneOperationsDataHelper, SceneLogicHelper { with SceneOperationsDataHelper, SceneLogicHelper {
const DeviceFunctionsView({super.key}); DeviceFunctionsView({super.key});
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -54,7 +54,7 @@ class DeviceFunctionsView extends StatelessWidget
context context
.read<CreateSceneBloc>() .read<CreateSceneBloc>()
.add(AddTaskEvent(isAutomation: isAutomation)); .add(AddTaskEvent(isAutomation: isAutomation));
navigateToRoute(context, Routes.sceneTasksRoute); navigateToRoute(context, Routes.sceneTasksRoute);
}, },
child: BodyMedium( child: BodyMedium(
text: 'Save', text: 'Save',

View File

@ -13,6 +13,8 @@ import 'package:syncrow_app/features/scene/widgets/scene_list_tile.dart';
import 'package:syncrow_app/features/shared_widgets/default_container.dart'; import 'package:syncrow_app/features/shared_widgets/default_container.dart';
import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart';
import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/generated/assets.dart';
import 'package:syncrow_app/navigation/navigate_to_route.dart';
import 'package:syncrow_app/navigation/routing_constants.dart';
import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; import 'package:syncrow_app/utils/resource_manager/strings_manager.dart';
@ -24,6 +26,7 @@ class SceneTasksView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
final sceneSettings = ModalRoute.of(context)!.settings.arguments final sceneSettings = ModalRoute.of(context)!.settings.arguments
as SceneSettingsRouteArguments; as SceneSettingsRouteArguments;
return DefaultScaffold( return DefaultScaffold(
title: sceneSettings.sceneName.isNotEmpty title: sceneSettings.sceneName.isNotEmpty
? sceneSettings.sceneName ? sceneSettings.sceneName
@ -107,8 +110,7 @@ class DeleteBottomSheetContent extends StatelessWidget {
listener: (context, state) { listener: (context, state) {
if (state is DeleteSceneSuccess) { if (state is DeleteSceneSuccess) {
if (state.success) { if (state.success) {
Navigator.pop(context); navigateToRoute(context, Routes.homeRoute);
Navigator.pop(context);
BlocProvider.of<SceneBloc>(context).add( BlocProvider.of<SceneBloc>(context).add(
LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); LoadScenes(HomeCubit.getInstance().selectedSpace!.id!));
} }

View File

@ -1,7 +1,6 @@
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_app/features/app_layout/bloc/home_cubit.dart'; import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart';
import 'package:syncrow_app/features/devices/view/widgets/scene_listview.dart';
import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_bloc.dart';
import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart';
@ -9,7 +8,9 @@ import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_grid_
import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_header.dart'; import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_header.dart';
import 'package:syncrow_app/features/shared_widgets/create_unit.dart'; import 'package:syncrow_app/features/shared_widgets/create_unit.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/body_medium.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/context_extension.dart';
import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class SceneView extends StatelessWidget { class SceneView extends StatelessWidget {
final bool pageType; final bool pageType;
@ -19,7 +20,8 @@ class SceneView extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocProvider( return BlocProvider(
create: (BuildContext context) => SceneBloc() create: (BuildContext context) => SceneBloc()
..add(LoadScenes(HomeCubit.getInstance().selectedSpace?.id ?? '')), ..add(LoadScenes(HomeCubit.getInstance().selectedSpace?.id ?? ''))
..add(LoadAutomation(HomeCubit.getInstance().selectedSpace?.id ?? '')),
child: BlocBuilder<CreateSceneBloc, CreateSceneState>( child: BlocBuilder<CreateSceneBloc, CreateSceneState>(
builder: (context, state) { builder: (context, state) {
if (state is DeleteSceneSuccess) { if (state is DeleteSceneSuccess) {
@ -32,6 +34,8 @@ class SceneView extends StatelessWidget {
if (state.success == true) { if (state.success == true) {
BlocProvider.of<SceneBloc>(context) BlocProvider.of<SceneBloc>(context)
.add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!)); .add(LoadScenes(HomeCubit.getInstance().selectedSpace!.id!));
BlocProvider.of<SceneBloc>(context).add(
LoadAutomation(HomeCubit.getInstance().selectedSpace!.id!));
} }
} }
return BlocListener<SceneBloc, SceneState>( return BlocListener<SceneBloc, SceneState>(
@ -63,29 +67,69 @@ class SceneView extends StatelessWidget {
); );
} }
if (state is SceneLoaded) { if (state is SceneLoaded) {
if (state.scenes.isNotEmpty) { final scenes = state.scenes;
return pageType == false final automationList = state.automationList;
? Expanded(
child: SceneGrid( return Expanded(
scenes: state.scenes, child: ListView(
loadingSceneId: state.loadingSceneId, children: [
ExpansionTile(
tilePadding: const EdgeInsets.symmetric(
horizontal: 6),
initiallyExpanded: true,
iconColor: ColorsManager.grayColor,
title: const BodySmall(
text: 'Tap to run routines'),
children: [
scenes.isNotEmpty
? SceneGrid(
scenes: scenes,
loadingSceneId:
state.loadingSceneId,
disablePLayButton: false,
)
: const Center(
child: BodyMedium(
text:
'No scenes have been added yet',
),
),
const SizedBox(
height: 10,
), ),
) ],
: Expanded(
child: SceneListview(
scenes: state.scenes,
loadingSceneId: state.loadingSceneId,
)
);
} else {
return const Expanded(
child: Center(
child: BodyMedium(
text: 'No scenes have been added yet',
), ),
), ExpansionTile(
); initiallyExpanded: true,
} iconColor: ColorsManager.grayColor,
tilePadding: const EdgeInsets.symmetric(
horizontal: 6),
title: const BodySmall(text: 'Automation'),
children: [
automationList.isNotEmpty
? SceneGrid(
scenes: automationList,
loadingSceneId:
state.loadingSceneId,
disablePLayButton: true,
)
: const Center(
child: BodyMedium(
text:
'No automations have been added yet',
),
),
const SizedBox(
height: 10,
),
],
),
const SizedBox(
height: 15,
),
],
),
);
} }
return const SizedBox(); return const SizedBox();
}, },

View File

@ -62,7 +62,7 @@ class _AlertDialogSliderStepsState extends State<AlertDialogSliderSteps> {
double _normalizeValue(dynamic value) { double _normalizeValue(dynamic value) {
if (widget.taskItem.code == "temp_set" || if (widget.taskItem.code == "temp_set" ||
widget.taskItem.code == "temp_current") { widget.taskItem.code == "temp_current") {
return (value as double) / 10; return (value) / 10;
} }
return value.toDouble(); return value.toDouble();
} }

View File

@ -83,8 +83,12 @@ class IFDefaultContainer extends StatelessWidget {
BlocBuilder<CreateSceneBloc, CreateSceneState>( BlocBuilder<CreateSceneBloc, CreateSceneState>(
builder: (context, state) { builder: (context, state) {
String conditionText = "When any condition is met"; String conditionText = "When any condition is met";
if (state is ConditionSelectedState) { if (state is AddSceneTask) {
conditionText = state.condition; if (state.condition == 'or') {
conditionText = "When any condition is met";
} else {
conditionText = "When all conditions are met";
}
} }
return SizedBox( return SizedBox(
width: context.width * 0.6, width: context.width * 0.6,

View File

@ -1,21 +1,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_app/features/scene/model/scenes_model.dart';
import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_item.dart'; import 'package:syncrow_app/features/scene/widgets/scene_view_widget/scene_item.dart';
class SceneGrid extends StatelessWidget { class SceneGrid extends StatelessWidget {
final List<ScenesModel> scenes; final List<dynamic> scenes;
final String? loadingSceneId; final String? loadingSceneId;
final bool disablePLayButton;
const SceneGrid({ const SceneGrid({
required this.scenes, required this.scenes,
required this.loadingSceneId, required this.loadingSceneId,
super.key, super.key,
required this.disablePLayButton,
}); });
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return return GridView.builder(
GridView.builder(
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2, crossAxisCount: 2,
mainAxisSpacing: 12, mainAxisSpacing: 12,
@ -26,7 +27,10 @@ class SceneGrid extends StatelessWidget {
itemBuilder: (context, index) { itemBuilder: (context, index) {
final scene = scenes[index]; final scene = scenes[index];
final isLoading = loadingSceneId == scene.id; final isLoading = loadingSceneId == scene.id;
return SceneItem(scene: scene, isLoading: isLoading); return SceneItem(
scene: scene,
isLoading: isLoading,
disablePLayButton: disablePLayButton);
}, },
); );
} }

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/body_small.dart';
import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart'; import 'package:syncrow_app/features/shared_widgets/text_widgets/title_medium.dart';
import 'package:syncrow_app/utils/resource_manager/strings_manager.dart'; import 'package:syncrow_app/utils/resource_manager/strings_manager.dart';
@ -20,10 +19,10 @@ class SceneHeader extends StatelessWidget {
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
), ),
), ),
SizedBox(height: 20), // SizedBox(height: 20),
BodySmall( // BodySmall(
text: StringsManager.tapToRunRoutine, // text: StringsManager.tapToRunRoutine,
), // ),
], ],
); );
} }

View File

@ -16,11 +16,13 @@ import 'package:syncrow_app/utils/resource_manager/color_manager.dart';
class SceneItem extends StatelessWidget { class SceneItem extends StatelessWidget {
final ScenesModel scene; final ScenesModel scene;
final bool isLoading; final bool isLoading;
final bool disablePLayButton;
const SceneItem({ const SceneItem({
required this.scene, required this.scene,
required this.isLoading, required this.isLoading,
super.key, super.key,
required this.disablePLayButton,
}); });
@override @override
@ -31,16 +33,28 @@ class SceneItem extends StatelessWidget {
context, context,
Routes.sceneTasksRoute, Routes.sceneTasksRoute,
arguments: SceneSettingsRouteArguments( arguments: SceneSettingsRouteArguments(
sceneType: CreateSceneEnum.tabToRun.name, sceneType: disablePLayButton == false
? CreateSceneEnum.tabToRun.name
: CreateSceneEnum.deviceStatusChanges.name,
sceneId: scene.id, sceneId: scene.id,
sceneName: scene.name, sceneName: scene.name,
), ),
); );
BlocProvider.of<CreateSceneBloc>(context) if (disablePLayButton == false) {
.add(FetchSceneTasksEvent(sceneId: scene.id)); BlocProvider.of<CreateSceneBloc>(context)
.add(const SceneTypeEvent(CreateSceneEnum.tabToRun));
BlocProvider.of<CreateSceneBloc>(context).add(
FetchSceneTasksEvent(sceneId: scene.id, isAutomation: false));
} else {
BlocProvider.of<CreateSceneBloc>(context)
.add(const SceneTypeEvent(CreateSceneEnum.deviceStatusChanges));
BlocProvider.of<CreateSceneBloc>(context)
.add(FetchSceneTasksEvent(sceneId: scene.id, isAutomation: true));
}
}, },
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Row( Row(
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
@ -52,22 +66,25 @@ class SceneItem extends StatelessWidget {
Assets.assetsIconsLogo, Assets.assetsIconsLogo,
fit: BoxFit.fill, fit: BoxFit.fill,
), ),
IconButton( Visibility(
padding: EdgeInsets.zero, visible: disablePLayButton == false,
onPressed: () { child: IconButton(
context padding: EdgeInsets.zero,
.read<SceneBloc>() onPressed: () {
.add(SceneTrigger(scene.id, scene.name)); context
}, .read<SceneBloc>()
icon: isLoading .add(SceneTrigger(scene.id, scene.name));
? const Center( },
child: CircularProgressIndicator(), icon: isLoading
) ? const Center(
: const Icon( child: CircularProgressIndicator(),
Icons.play_circle, )
size: 40, : const Icon(
color: ColorsManager.greyColor, Icons.play_circle,
), size: 40,
color: ColorsManager.greyColor,
),
),
), ),
], ],
), ),

View File

@ -24,54 +24,67 @@ class Router {
static Route<dynamic> generateRoute(RouteSettings settings) { static Route<dynamic> generateRoute(RouteSettings settings) {
switch (settings.name) { switch (settings.name) {
case Routes.splash: case Routes.splash:
return MaterialPageRoute(builder: (_) => const SplashView(), settings: settings); return MaterialPageRoute(
builder: (_) => const SplashView(), settings: settings);
// case Routes.devicesRoute: // case Routes.devicesRoute:
// return MaterialPageRoute( // return MaterialPageRoute(
// builder: (_) => const DevicesView(), settings: settings); // builder: (_) => const DevicesView(), settings: settings);
case Routes.profileRoute: case Routes.profileRoute:
return MaterialPageRoute(builder: (_) => const ProfileView(), settings: settings); return MaterialPageRoute(
builder: (_) => const ProfileView(), settings: settings);
case Routes.sceneRoute: case Routes.sceneRoute:
return MaterialPageRoute(builder: (_) => const SceneView(), settings: settings); return MaterialPageRoute(
builder: (_) => const SceneView(), settings: settings);
case Routes.layoutRoute: case Routes.layoutRoute:
return MaterialPageRoute(builder: (_) => const LayoutPage(), settings: settings); return MaterialPageRoute(
builder: (_) => const LayoutPage(), settings: settings);
case Routes.authLogin: case Routes.authLogin:
return MaterialPageRoute(builder: (_) => const LoginView(), settings: settings); return MaterialPageRoute(
builder: (_) => const LoginView(), settings: settings);
case Routes.otpRoute: case Routes.otpRoute:
return MaterialPageRoute(builder: (_) => const OtpView(), settings: settings); return MaterialPageRoute(
builder: (_) => const OtpView(), settings: settings);
case Routes.authSignUp: case Routes.authSignUp:
return MaterialPageRoute(builder: (_) => const SignUpView(), settings: settings); return MaterialPageRoute(
builder: (_) => const SignUpView(), settings: settings);
case Routes.dashboardRoute: case Routes.dashboardRoute:
return MaterialPageRoute(builder: (_) => const DashboardView(), settings: settings); return MaterialPageRoute(
builder: (_) => const DashboardView(), settings: settings);
case Routes.homeRoute: case Routes.homeRoute:
return MaterialPageRoute(builder: (_) => const AppLayout(), settings: settings); return MaterialPageRoute(
builder: (_) => const AppLayout(), settings: settings);
case Routes.menuRoute: case Routes.menuRoute:
return MaterialPageRoute(builder: (_) => const MenuView(), settings: settings); return MaterialPageRoute(
builder: (_) => const MenuView(), settings: settings);
case Routes.createUnit: case Routes.createUnit:
return MaterialPageRoute(builder: (_) => const CreateUnitView(), settings: settings); return MaterialPageRoute(
builder: (_) => const CreateUnitView(), settings: settings);
case Routes.sceneTasksRoute: case Routes.sceneTasksRoute:
return MaterialPageRoute(builder: (_) => const SceneTasksView(), settings: settings); return MaterialPageRoute(
builder: (_) => const SceneTasksView(), settings: settings);
case Routes.sceneControlDevicesRoute: case Routes.sceneControlDevicesRoute:
return MaterialPageRoute( return MaterialPageRoute(
builder: (_) => MultiBlocProvider( builder: (_) => MultiBlocProvider(
providers: [ providers: [
BlocProvider( BlocProvider(
create: (BuildContext context) => DeviceManagerBloc()..add(FetchAllDevices()), create: (BuildContext context) =>
DeviceManagerBloc()..add(FetchAllDevices()),
), ),
BlocProvider( BlocProvider(
create: (BuildContext context) => create: (BuildContext context) => TabBarBloc(
TabBarBloc(context.read<DeviceManagerBloc>()) context.read<DeviceManagerBloc>())
..add(const TabChanged(selectedIndex: 0, roomId: '-1')), ..add(const TabChanged(selectedIndex: 0, roomId: '-1')),
), ),
], ],
child: const SceneRoomsTabBarDevicesView(), child: const SceneRoomsTabBarDevicesView(),
@ -79,7 +92,7 @@ class Router {
settings: settings); settings: settings);
case Routes.deviceFunctionsRoute: case Routes.deviceFunctionsRoute:
return MaterialPageRoute( return MaterialPageRoute(
builder: (_) => const DeviceFunctionsView(), builder: (_) => DeviceFunctionsView(),
settings: settings, settings: settings,
); );
default: default:

View File

@ -129,7 +129,7 @@ abstract class ApiEndpoints {
static const String assignDeviceToRoom = '$baseUrl/device/room'; static const String assignDeviceToRoom = '$baseUrl/device/room';
/// Scene API //////////////////// /// Scene & Automation API ////////////////////
/// POST /// POST
static const String createScene = '$baseUrl/scene/tap-to-run'; static const String createScene = '$baseUrl/scene/tap-to-run';
static const String triggerScene = static const String triggerScene =
@ -142,9 +142,16 @@ abstract class ApiEndpoints {
static const String getScene = '$baseUrl/scene/tap-to-run/details/{sceneId}'; static const String getScene = '$baseUrl/scene/tap-to-run/details/{sceneId}';
static const String getUnitAutomation = '$baseUrl/automation/{unitUuid}';
static const String getAutomationDetails = '$baseUrl/automation/details/{automationId}';
/// PUT /// PUT
static const String updateScene = '$baseUrl/scene/tap-to-run/{sceneId}'; static const String updateScene = '$baseUrl/scene/tap-to-run/{sceneId}';
static const String updateAutomation =
'$baseUrl/automation/{automationId}';
/// DELETE /// DELETE
static const String deleteScene = static const String deleteScene =
'$baseUrl/scene/tap-to-run/{unitUuid}/{sceneId}'; '$baseUrl/scene/tap-to-run/{unitUuid}/{sceneId}';

View File

@ -8,6 +8,7 @@ import 'package:syncrow_app/services/api/http_service.dart';
class SceneApi { class SceneApi {
static final HTTPService _httpService = HTTPService(); static final HTTPService _httpService = HTTPService();
//create scene
static Future<Map<String, dynamic>> createScene( static Future<Map<String, dynamic>> createScene(
CreateSceneModel createSceneModel) async { CreateSceneModel createSceneModel) async {
try { try {
@ -25,6 +26,7 @@ class SceneApi {
} }
} }
// create automation
static Future<Map<String, dynamic>> createAutomation( static Future<Map<String, dynamic>> createAutomation(
CreateAutomationModel createAutomationModel) async { CreateAutomationModel createAutomationModel) async {
try { try {
@ -42,6 +44,8 @@ class SceneApi {
} }
} }
//get scene by unit id
static Future<List<ScenesModel>> getScenesByUnitId(String unitId) async { static Future<List<ScenesModel>> getScenesByUnitId(String unitId) async {
try { try {
final response = await _httpService.get( final response = await _httpService.get(
@ -61,6 +65,27 @@ class SceneApi {
} }
} }
//getAutomation
static Future<List<ScenesModel>> getAutomationByUnitId(String unitId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getUnitAutomation.replaceAll('{unitUuid}', unitId),
showServerMessage: false,
expectedResponseModel: (json) {
List<ScenesModel> scenes = [];
for (var scene in json) {
scenes.add(ScenesModel.fromJson(scene));
}
return scenes;
},
);
return response;
} catch (e) {
rethrow;
}
}
static Future<bool> triggerScene(String sceneId) async { static Future<bool> triggerScene(String sceneId) async {
try { try {
final response = await _httpService.post( final response = await _httpService.post(
@ -74,6 +99,22 @@ class SceneApi {
} }
} }
//automation details
static Future<SceneDetailsModel> getAutomationDetails(
String automationId) async {
try {
final response = await _httpService.get(
path: ApiEndpoints.getAutomationDetails
.replaceAll('{automationId}', automationId),
showServerMessage: false,
expectedResponseModel: (json) => SceneDetailsModel.fromJson(json),
);
return response;
} catch (e) {
rethrow;
}
}
//getScene //getScene
static Future<SceneDetailsModel> getSceneDetails(String sceneId) async { static Future<SceneDetailsModel> getSceneDetails(String sceneId) async {
@ -89,7 +130,7 @@ class SceneApi {
} }
} }
//updateScene //update Scene
static updateScene(CreateSceneModel createSceneModel, String sceneId) async { static updateScene(CreateSceneModel createSceneModel, String sceneId) async {
try { try {
final response = await _httpService.put( final response = await _httpService.put(
@ -106,7 +147,26 @@ class SceneApi {
} }
} }
//deleteScene //update automation
static updateAutomation(
CreateAutomationModel createAutomationModel, String automationId) async {
try {
final response = await _httpService.put(
path: ApiEndpoints.updateAutomation
.replaceAll('{automationId}', automationId),
body: createAutomationModel
.toJson(automationId.isNotEmpty == true ? automationId : null),
expectedResponseModel: (json) {
return json;
},
);
return response;
} catch (e) {
rethrow;
}
}
//delete Scene
static Future<bool> deleteScene( static Future<bool> deleteScene(
{required String unitUuid, required String sceneId}) async { {required String unitUuid, required String sceneId}) async {