From 17881694e0cccf87e4beaa6be6ed2bdf32562f2b Mon Sep 17 00:00:00 2001 From: ashrafzarkanisala Date: Thu, 27 Jun 2024 02:35:50 +0300 Subject: [PATCH] connected all apis , create functionality is working --- .../bloc/create_scene/create_scene_bloc.dart | 30 ++- .../bloc/create_scene/create_scene_event.dart | 21 +- .../bloc/create_scene/create_scene_state.dart | 18 +- .../scene/bloc/scene_bloc/scene_bloc.dart | 36 ++- .../scene/bloc/scene_bloc/scene_event.dart | 19 +- .../scene/bloc/scene_bloc/scene_state.dart | 29 ++- .../scene/helper/scene_logic_helper.dart | 69 ++++++ ...dart => scene_operations_data_helper.dart} | 105 ++++++--- .../scene/model/create_scene_model.dart | 222 ++++++++++++++++++ lib/features/scene/model/scene_model.dart | 76 +++--- .../scene/model/scene_static_function.dart | 21 +- lib/features/scene/repo/scene_repo.dart | 16 -- .../scene/view/create_scene_view.dart | 4 +- .../scene/view/device_functions_view.dart | 13 +- lib/features/scene/view/scene_tasks_view.dart | 16 +- lib/features/scene/view/scene_view.dart | 12 +- .../alert_dialog_countdown.dart | 6 +- .../alert_dialog_functions_body.dart | 6 +- .../alert_dialog_temperature_body.dart | 7 +- .../widgets/create_scene_save_button.dart | 106 +++++++++ lib/services/api/api_links_endpoints.dart | 45 +++- lib/services/api/scene_api.dart | 57 +++++ lib/utils/context_extension.dart | 13 + 23 files changed, 793 insertions(+), 154 deletions(-) create mode 100644 lib/features/scene/helper/scene_logic_helper.dart rename lib/features/scene/helper/{scene_helper.dart => scene_operations_data_helper.dart} (78%) create mode 100644 lib/features/scene/model/create_scene_model.dart delete mode 100644 lib/features/scene/repo/scene_repo.dart rename lib/features/scene/widgets/{ => alert_dialogs}/alert_dialog_countdown.dart (91%) rename lib/features/scene/widgets/{ => alert_dialogs}/alert_dialog_functions_body.dart (96%) rename lib/features/scene/widgets/{ => alert_dialogs}/alert_dialog_temperature_body.dart (94%) create mode 100644 lib/features/scene/widgets/create_scene_save_button.dart create mode 100644 lib/services/api/scene_api.dart diff --git a/lib/features/scene/bloc/create_scene/create_scene_bloc.dart b/lib/features/scene/bloc/create_scene/create_scene_bloc.dart index 413cfbe..a2cb00c 100644 --- a/lib/features/scene/bloc/create_scene/create_scene_bloc.dart +++ b/lib/features/scene/bloc/create_scene/create_scene_bloc.dart @@ -3,21 +3,24 @@ import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart'; +import 'package:syncrow_app/features/scene/model/create_scene_model.dart'; import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/services/api/scene_api.dart'; part 'create_scene_event.dart'; part 'create_scene_state.dart'; class CreateSceneBloc extends Bloc { CreateSceneBloc() : super(CreateSceneInitial()) { - on((event, emit) => null as FutureOr); + on(_createSceneWithTasks); on(_onAddSceneTask); on(_selectedValue); on(_removeTaskById); + on(_clearTaskList); } List tasksList = []; - String selectedValue = ''; + dynamic selectedValue; FutureOr _onAddSceneTask( AddTaskEvent event, Emitter emit) { @@ -58,4 +61,27 @@ class CreateSceneBloc extends Bloc { } } } + + FutureOr _createSceneWithTasks( + CreateSceneWithTasksEvent event, Emitter emit) async { + emit(CreateSceneLoading()); + try { + final response = await SceneApi.createScene(event.createSceneModel); + if (response['success'] == true) { + tasksList.clear(); + emit(const CreateSceneWithTasks(success: true)); + } else { + emit(const CreateSceneError(message: 'Something went wrong')); + } + } catch (e) { + emit(CreateSceneError(message: e.toString())); + emit(AddSceneTask(tasksList: tasksList)); + } + } + + FutureOr _clearTaskList( + ClearTaskListEvent event, Emitter emit) { + tasksList.clear(); + emit(AddSceneTask(tasksList: tasksList)); + } } diff --git a/lib/features/scene/bloc/create_scene/create_scene_event.dart b/lib/features/scene/bloc/create_scene/create_scene_event.dart index a91843a..8f07a0b 100644 --- a/lib/features/scene/bloc/create_scene/create_scene_event.dart +++ b/lib/features/scene/bloc/create_scene/create_scene_event.dart @@ -28,12 +28,12 @@ class AddTaskEvent extends CreateSceneEvent { } class SelectedValueEvent extends CreateSceneEvent { - final String value; + final dynamic value; - const SelectedValueEvent({required this.value}); + const SelectedValueEvent({this.value}); @override - List get props => [value]; + List get props => [value!]; } class RemoveTaskEvent extends CreateSceneEvent { @@ -44,3 +44,18 @@ class RemoveTaskEvent extends CreateSceneEvent { @override List get props => [taskId]; } + +class CreateSceneWithTasksEvent extends CreateSceneEvent { + final CreateSceneModel createSceneModel; + const CreateSceneWithTasksEvent({required this.createSceneModel}); + + @override + List get props => [createSceneModel]; +} + +class ClearTaskListEvent extends CreateSceneEvent { + const ClearTaskListEvent(); + + @override + List get props => []; +} diff --git a/lib/features/scene/bloc/create_scene/create_scene_state.dart b/lib/features/scene/bloc/create_scene/create_scene_state.dart index 241c04a..95d8e20 100644 --- a/lib/features/scene/bloc/create_scene/create_scene_state.dart +++ b/lib/features/scene/bloc/create_scene/create_scene_state.dart @@ -11,6 +11,14 @@ final class CreateSceneInitial extends CreateSceneState {} class CreateSceneLoading extends CreateSceneState {} +class CreateSceneError extends CreateSceneState { + final String message; + const CreateSceneError({required this.message}); + + @override + List get props => [message]; +} + class AddSceneTask extends CreateSceneState { final List tasksList; const AddSceneTask({required this.tasksList}); @@ -20,9 +28,17 @@ class AddSceneTask extends CreateSceneState { } class SelectedTaskValueState extends CreateSceneState { - final String value; + final dynamic value; const SelectedTaskValueState({required this.value}); @override List get props => [value]; } + +class CreateSceneWithTasks extends CreateSceneState { + final bool success; + const CreateSceneWithTasks({required this.success}); + + @override + List get props => [success]; +} diff --git a/lib/features/scene/bloc/scene_bloc/scene_bloc.dart b/lib/features/scene/bloc/scene_bloc/scene_bloc.dart index 32de4bb..81d04d3 100644 --- a/lib/features/scene/bloc/scene_bloc/scene_bloc.dart +++ b/lib/features/scene/bloc/scene_bloc/scene_bloc.dart @@ -1,28 +1,48 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/scene/bloc/scene_bloc/scene_event.dart'; import 'package:syncrow_app/features/scene/model/scene_model.dart'; +import 'package:syncrow_app/services/api/scene_api.dart'; part 'scene_state.dart'; class SceneBloc extends Bloc { SceneBloc() : super(SceneInitial()) { on(_onLoadScenes); + on(_sceneTrigger); } - void _onLoadScenes(LoadScenes event, Emitter emit) { + FutureOr _onLoadScenes( + LoadScenes event, Emitter emit) async { emit(SceneLoading()); try { - final scenes = _loadScenes(); - emit(SceneLoaded(scenes)); - } catch (_) { - emit(SceneError()); + final scenes = await SceneApi.getScenesByUnitId(event.unitId); + if (scenes.isNotEmpty) { + emit(SceneLoaded(scenes)); + } else { + emit(SceneTriggerError(message: 'Something went wrong')); + } + } catch (e) { + emit(SceneError(message: e.toString())); } } - List _loadScenes() { - //TODO: Load scenes - return []; + FutureOr _sceneTrigger( + SceneTrigger event, Emitter emit) async { + emit(SceneTriggerLoading()); + + try { + final success = await SceneApi.triggerScene(event.sceneId); + if (success) { + emit(SceneTriggerState(success: true)); + } else { + emit(SceneTriggerError(message: 'Something went wrong')); + } + } catch (e) { + emit(SceneTriggerError(message: e.toString())); + } } } diff --git a/lib/features/scene/bloc/scene_bloc/scene_event.dart b/lib/features/scene/bloc/scene_bloc/scene_event.dart index f1d3739..8c05921 100644 --- a/lib/features/scene/bloc/scene_bloc/scene_event.dart +++ b/lib/features/scene/bloc/scene_bloc/scene_event.dart @@ -1,4 +1,3 @@ - import 'package:equatable/equatable.dart'; abstract class SceneEvent extends Equatable { @@ -6,4 +5,20 @@ abstract class SceneEvent extends Equatable { List get props => []; } -class LoadScenes extends SceneEvent {} \ No newline at end of file +class LoadScenes extends SceneEvent { + final String unitId; + + LoadScenes(this.unitId); + + @override + List get props => [unitId]; +} + +class SceneTrigger extends SceneEvent { + final String sceneId; + + SceneTrigger(this.sceneId); + + @override + List get props => [sceneId]; +} diff --git a/lib/features/scene/bloc/scene_bloc/scene_state.dart b/lib/features/scene/bloc/scene_bloc/scene_state.dart index a71f6ab..136c7ba 100644 --- a/lib/features/scene/bloc/scene_bloc/scene_state.dart +++ b/lib/features/scene/bloc/scene_bloc/scene_state.dart @@ -18,4 +18,31 @@ class SceneLoaded extends SceneState { List get props => [scenes]; } -class SceneError extends SceneState {} +class SceneError extends SceneState { + final String message; + + SceneError({required this.message}); + + @override + List get props => [message]; +} + +class SceneTriggerState extends SceneState { + final bool success; + + SceneTriggerState({required this.success}); + + @override + List get props => [success]; +} + +class SceneTriggerLoading extends SceneState {} + +class SceneTriggerError extends SceneState { + final String message; + + SceneTriggerError({required this.message}); + + @override + List get props => [message]; +} diff --git a/lib/features/scene/helper/scene_logic_helper.dart b/lib/features/scene/helper/scene_logic_helper.dart new file mode 100644 index 0000000..cae5a29 --- /dev/null +++ b/lib/features/scene/helper/scene_logic_helper.dart @@ -0,0 +1,69 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_app/features/app_layout/bloc/home_cubit.dart'; +import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; +import 'package:syncrow_app/features/scene/model/create_scene_model.dart'; +import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; + +mixin SceneLogicHelper { + bool isOnlyDelayOrDelayLast(List tasks) { + final lastTask = tasks.last; + return tasks.every((task) => task.code == 'delay') || + lastTask.code == 'delay'; + } + + void handleSaveButtonPress( + BuildContext context, + TextEditingController sceneNameController, + List tasks, + ) { + if (isOnlyDelayOrDelayLast(tasks)) { + // Show snackbar indicating restriction + context.showCustomSnackbar( + message: 'Only a single delay or delay-last operations are allowed.', + icon: const Icon( + Icons.error, + color: Colors.red, + ), + ); + } else { + final createSceneModel = CreateSceneModel( + unitUuid: HomeCubit.getInstance().selectedSpace!.id ?? '', + sceneName: sceneNameController.text, + decisionExpr: 'and', + actions: List.generate( + tasks.length, + (index) { + final task = tasks[index]; + if (task.code == 'delay') { + return CreateSceneAction( + entityId: tasks[index].deviceId, + actionExecutor: 'delay', + executorProperty: CreateSceneExecutorProperty( + functionCode: task.code, + functionValue: task.operationalValues.first.value, + delaySeconds: 0, + ), + ); + } + return CreateSceneAction( + entityId: task.deviceId, + actionExecutor: 'device_issue', + executorProperty: CreateSceneExecutorProperty( + functionCode: task.code, + functionValue: task.operationalValues.first.value, + delaySeconds: 0, + ), + ); + }, + ), + ); + context + .read() + .add(CreateSceneWithTasksEvent(createSceneModel: createSceneModel)); + + Navigator.pop(context); + } + } +} diff --git a/lib/features/scene/helper/scene_helper.dart b/lib/features/scene/helper/scene_operations_data_helper.dart similarity index 78% rename from lib/features/scene/helper/scene_helper.dart rename to lib/features/scene/helper/scene_operations_data_helper.dart index 859e9b5..3072d5a 100644 --- a/lib/features/scene/helper/scene_helper.dart +++ b/lib/features/scene/helper/scene_operations_data_helper.dart @@ -4,7 +4,7 @@ import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/resource_manager/constants.dart'; -mixin SceneHelper { +mixin SceneOperationsDataHelper { List getFunctionsWithIcons({ DeviceType? type, required List functions, @@ -79,25 +79,25 @@ mixin SceneHelper { code: 'sensitivity', operationalValues: [ SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "1"), + icon: Assets.assetsSensitivityOperationIcon, value: 1), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "2"), + icon: Assets.assetsSensitivityOperationIcon, value: 2), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "3"), + icon: Assets.assetsSensitivityOperationIcon, value: 3), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "4"), + icon: Assets.assetsSensitivityOperationIcon, value: 4), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "5"), + icon: Assets.assetsSensitivityOperationIcon, value: 5), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "6"), + icon: Assets.assetsSensitivityOperationIcon, value: 6), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "7"), + icon: Assets.assetsSensitivityOperationIcon, value: 7), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "8"), + icon: Assets.assetsSensitivityOperationIcon, value: 8), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "9"), + icon: Assets.assetsSensitivityOperationIcon, value: 9), SceneOperationalValue( - icon: Assets.assetsSensitivityOperationIcon, value: "10"), + icon: Assets.assetsSensitivityOperationIcon, value: 10), ], ), ]; @@ -151,10 +151,15 @@ List threeGangFunctions( operationName: 'Light 1 Switch', code: 'switch_1', operationalValues: [ - SceneOperationalValue(icon: Assets.assetsAcPower, value: "ON"), - SceneOperationalValue(icon: Assets.assetsAcPowerOFF, value: "OFF"), SceneOperationalValue( - icon: Assets.assetsSceneRefresh, value: "Reverse Switch"), + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + SceneOperationalValue( + icon: Assets.assetsSceneRefresh, + description: "Reverse Switch", + value: false, + ), ], ), SceneStaticFunction( @@ -164,10 +169,15 @@ List threeGangFunctions( operationName: 'Light 2 Switch', code: 'switch_2', operationalValues: [ - SceneOperationalValue(icon: Assets.assetsAcPower, value: "ON"), - SceneOperationalValue(icon: Assets.assetsAcPowerOFF, value: "OFF"), SceneOperationalValue( - icon: Assets.assetsSceneRefresh, value: "Reverse Switch"), + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + SceneOperationalValue( + icon: Assets.assetsSceneRefresh, + description: "Reverse Switch", + value: false, + ), ], ), SceneStaticFunction( @@ -177,10 +187,15 @@ List threeGangFunctions( operationName: 'Light 3 Switch', code: 'switch_3', operationalValues: [ - SceneOperationalValue(icon: Assets.assetsAcPower, value: "ON"), - SceneOperationalValue(icon: Assets.assetsAcPowerOFF, value: "OFF"), SceneOperationalValue( - icon: Assets.assetsSceneRefresh, value: "Reverse Switch"), + icon: Assets.assetsAcPower, description: "ON", value: true), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, description: "OFF", value: false), + SceneOperationalValue( + icon: Assets.assetsSceneRefresh, + description: "Reverse Switch", + value: false, + ), ], ), SceneStaticFunction( @@ -190,7 +205,7 @@ List threeGangFunctions( operationName: 'Light 1 CountDown', code: 'countdown_1', operationalValues: [ - SceneOperationalValue(icon: '', value: "0"), + SceneOperationalValue(icon: '', value: 0), ], ), SceneStaticFunction( @@ -200,7 +215,7 @@ List threeGangFunctions( operationName: 'Light 2 CountDown', code: 'countdown_1', operationalValues: [ - SceneOperationalValue(icon: '', value: "0"), + SceneOperationalValue(icon: '', value: 0), ], ), SceneStaticFunction( @@ -210,7 +225,7 @@ List threeGangFunctions( operationName: 'Light 3 CountDown', code: 'countdown_1', operationalValues: [ - SceneOperationalValue(icon: '', value: "0"), + SceneOperationalValue(icon: '', value: 0), ], ), ]; @@ -229,8 +244,16 @@ List acFunctions( operationName: 'Power', code: 'switch', operationalValues: [ - SceneOperationalValue(icon: Assets.assetsAcPower, value: "ON"), - SceneOperationalValue(icon: Assets.assetsAcPowerOFF, value: "OFF"), + SceneOperationalValue( + icon: Assets.assetsAcPower, + description: "ON", + value: true, + ), + SceneOperationalValue( + icon: Assets.assetsAcPowerOFF, + description: "OFF", + value: false, + ), ], ), SceneStaticFunction( @@ -242,15 +265,18 @@ List acFunctions( operationalValues: [ SceneOperationalValue( icon: Assets.assetsAcCooling, - value: AcValuesEnums.Cooling.name, + description: AcValuesEnums.Cooling.name, + value: TempModes.cold.name, ), SceneOperationalValue( icon: Assets.assetsAcHeating, - value: AcValuesEnums.Heating.name, + description: AcValuesEnums.Heating.name, + value: TempModes.hot.name, ), SceneOperationalValue( icon: Assets.assetsFanSpeed, - value: AcValuesEnums.Ventilation.name, + description: AcValuesEnums.Ventilation.name, + value: TempModes.wind.name, ), ], ), @@ -262,7 +288,10 @@ List acFunctions( code: 'temp_set', operationalValues: [ SceneOperationalValue( - icon: Assets.assetsCelsiusDegrees, value: "COOL TO"), + icon: Assets.assetsCelsiusDegrees, + value: 0, + description: 'COOL TO', + ), ], ), SceneStaticFunction( @@ -274,19 +303,23 @@ List acFunctions( operationalValues: [ SceneOperationalValue( icon: Assets.assetsAcFanLow, - value: ValueACRange.LOW.name, + description: ValueACRange.LOW.name, + value: FanSpeeds.low.name, ), SceneOperationalValue( icon: Assets.assetsAcFanMiddle, - value: ValueACRange.MIDDLE.name, + description: ValueACRange.MIDDLE.name, + value: FanSpeeds.middle.name, ), SceneOperationalValue( icon: Assets.assetsAcFanHigh, - value: ValueACRange.HIGH.name, + description: ValueACRange.HIGH.name, + value: FanSpeeds.high.name, ), SceneOperationalValue( icon: Assets.assetsAcFanAuto, - value: ValueACRange.AUTO.name, + description: ValueACRange.AUTO.name, + value: FanSpeeds.auto.name, ), ], ), @@ -299,11 +332,13 @@ List acFunctions( operationalValues: [ SceneOperationalValue( icon: Assets.assetsSceneChildLock, - value: 'Lock', + description: 'Lock', + value: true, ), SceneOperationalValue( icon: Assets.assetsSceneChildUnlock, - value: 'Unlock', + description: 'Unlock', + value: false, ), ], ), diff --git a/lib/features/scene/model/create_scene_model.dart b/lib/features/scene/model/create_scene_model.dart new file mode 100644 index 0000000..276ebeb --- /dev/null +++ b/lib/features/scene/model/create_scene_model.dart @@ -0,0 +1,222 @@ +import 'dart:convert'; + +import 'package:flutter/foundation.dart'; + +class CreateSceneModel { + /* +{ + "unitUuid": "string", + "sceneName": "string", + "decisionExpr": "string", + "actions": [ + { + "entityId": "string", + "actionExecutor": "string", + "executorProperty": { + "functionCode": "string", + "functionValue": {}, + "delaySeconds": 0 + } + } + ] +} + */ + + String unitUuid; + String sceneName; + String decisionExpr; + List actions; + CreateSceneModel({ + required this.unitUuid, + required this.sceneName, + required this.decisionExpr, + required this.actions, + }); + + CreateSceneModel copyWith({ + String? unitUuid, + String? sceneName, + String? decisionExpr, + List? actions, + }) { + return CreateSceneModel( + unitUuid: unitUuid ?? this.unitUuid, + sceneName: sceneName ?? this.sceneName, + decisionExpr: decisionExpr ?? this.decisionExpr, + actions: actions ?? this.actions, + ); + } + + Map toMap() { + return { + 'unitUuid': unitUuid, + 'sceneName': sceneName, + 'decisionExpr': decisionExpr, + 'actions': actions.map((x) => x.toMap()).toList(), + }; + } + + factory CreateSceneModel.fromMap(Map map) { + return CreateSceneModel( + unitUuid: map['unitUuid'] ?? '', + sceneName: map['sceneName'] ?? '', + decisionExpr: map['decisionExpr'] ?? '', + actions: List.from( + map['actions']?.map((x) => CreateSceneAction.fromMap(x))), + ); + } + + String toJson() => json.encode(toMap()); + + factory CreateSceneModel.fromJson(String source) => + CreateSceneModel.fromMap(json.decode(source)); + + @override + String toString() { + return 'CreateSceneModel(unitUuid: $unitUuid, sceneName: $sceneName, decisionExpr: $decisionExpr, actions: $actions)'; + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneModel && + other.unitUuid == unitUuid && + other.sceneName == sceneName && + other.decisionExpr == decisionExpr && + listEquals(other.actions, actions); + } + + @override + int get hashCode { + return unitUuid.hashCode ^ + sceneName.hashCode ^ + decisionExpr.hashCode ^ + actions.hashCode; + } +} + +class CreateSceneAction { + String entityId; + String actionExecutor; + CreateSceneExecutorProperty executorProperty; + CreateSceneAction({ + required this.entityId, + required this.actionExecutor, + required this.executorProperty, + }); + + CreateSceneAction copyWith({ + String? entityId, + String? actionExecutor, + CreateSceneExecutorProperty? executorProperty, + }) { + return CreateSceneAction( + entityId: entityId ?? this.entityId, + actionExecutor: actionExecutor ?? this.actionExecutor, + executorProperty: executorProperty ?? this.executorProperty, + ); + } + + Map toMap() { + return { + 'entityId': entityId, + 'actionExecutor': actionExecutor, + 'executorProperty': executorProperty.toMap(), + }; + } + + factory CreateSceneAction.fromMap(Map map) { + return CreateSceneAction( + entityId: map['entityId'] ?? '', + actionExecutor: map['actionExecutor'] ?? '', + executorProperty: + CreateSceneExecutorProperty.fromMap(map['executorProperty']), + ); + } + + String toJson() => json.encode(toMap()); + + factory CreateSceneAction.fromJson(String source) => + CreateSceneAction.fromMap(json.decode(source)); + + @override + String toString() => + 'CreateSceneAction(entityId: $entityId, actionExecutor: $actionExecutor, executorProperty: $executorProperty)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneAction && + other.entityId == entityId && + other.actionExecutor == actionExecutor && + other.executorProperty == executorProperty; + } + + @override + int get hashCode => + entityId.hashCode ^ actionExecutor.hashCode ^ executorProperty.hashCode; +} + +class CreateSceneExecutorProperty { + String functionCode; + dynamic functionValue; + int delaySeconds; + CreateSceneExecutorProperty({ + required this.functionCode, + required this.functionValue, + required this.delaySeconds, + }); + + CreateSceneExecutorProperty copyWith({ + String? functionCode, + dynamic functionValue, + int? delaySeconds, + }) { + return CreateSceneExecutorProperty( + functionCode: functionCode ?? this.functionCode, + functionValue: functionValue ?? this.functionValue, + delaySeconds: delaySeconds ?? this.delaySeconds, + ); + } + + Map toMap() { + return { + 'functionCode': functionCode, + 'functionValue': functionValue, + 'delaySeconds': delaySeconds, + }; + } + + factory CreateSceneExecutorProperty.fromMap(Map map) { + return CreateSceneExecutorProperty( + functionCode: map['functionCode'] ?? '', + functionValue: map['functionValue'] ?? '', + delaySeconds: map['delaySeconds']?.toInt() ?? 0, + ); + } + + String toJson() => json.encode(toMap()); + + factory CreateSceneExecutorProperty.fromJson(String source) => + CreateSceneExecutorProperty.fromMap(json.decode(source)); + + @override + String toString() => + 'CreateSceneExecutorProperty(functionCode: $functionCode, functionValue: $functionValue, delaySeconds: $delaySeconds)'; + + @override + bool operator ==(Object other) { + if (identical(this, other)) return true; + + return other is CreateSceneExecutorProperty && + other.functionCode == functionCode && + other.functionValue == functionValue && + other.delaySeconds == delaySeconds; + } + + @override + int get hashCode => + functionCode.hashCode ^ functionValue.hashCode ^ delaySeconds.hashCode; +} diff --git a/lib/features/scene/model/scene_model.dart b/lib/features/scene/model/scene_model.dart index f360ec2..bd61a5d 100644 --- a/lib/features/scene/model/scene_model.dart +++ b/lib/features/scene/model/scene_model.dart @@ -1,46 +1,54 @@ +import 'dart:convert'; + class SceneModel { final String id; final String name; - final String description; - final String imageUrl; - final String location; - final String type; - final String rating; - final String price; - final String duration; - final String date; - final String time; - final String status; + final Status status; + final Type type; SceneModel({ required this.id, required this.name, - required this.description, - required this.imageUrl, - required this.location, - required this.type, - required this.rating, - required this.price, - required this.duration, - required this.date, - required this.time, required this.status, + required this.type, }); - factory SceneModel.fromJson(Map json) { - return SceneModel( - id: json['id'], - name: json['name'], - description: json['description'], - imageUrl: json['imageUrl'], - location: json['location'], - type: json['type'], - rating: json['rating'], - price: json['price'], - duration: json['duration'], - date: json['date'], - time: json['time'], - status: json['status'], - ); + factory SceneModel.fromRawJson(String str) => + SceneModel.fromJson(json.decode(str)); + + String toRawJson() => json.encode(toJson()); + + factory SceneModel.fromJson(Map json) => SceneModel( + id: json["id"], + name: json["name"], + status: statusValues.map[json["status"]]!, + type: typeValues.map[json["type"]]!, + ); + + Map toJson() => { + "id": id, + "name": name, + "status": statusValues.reverse[status], + "type": typeValues.reverse[type], + }; +} + +enum Status { ENABLE } + +final statusValues = EnumValues({"enable": Status.ENABLE}); + +enum Type { TAP_TO_RUN } + +final typeValues = EnumValues({"tap_to_run": Type.TAP_TO_RUN}); + +class EnumValues { + Map map; + late Map reverseMap; + + EnumValues(this.map); + + Map get reverse { + reverseMap = map.map((k, v) => MapEntry(v, k)); + return reverseMap; } } diff --git a/lib/features/scene/model/scene_static_function.dart b/lib/features/scene/model/scene_static_function.dart index 730cc41..5b1e6dd 100644 --- a/lib/features/scene/model/scene_static_function.dart +++ b/lib/features/scene/model/scene_static_function.dart @@ -99,34 +99,36 @@ class SceneStaticFunction { class SceneOperationalValue { final String icon; - final String value; + final dynamic value; + final String? description; SceneOperationalValue({ required this.icon, required this.value, + this.description, }); SceneOperationalValue copyWith({ String? icon, - String? value, + dynamic value, + String? description, }) { return SceneOperationalValue( icon: icon ?? this.icon, value: value ?? this.value, + description: description ?? this.description, ); } Map toMap() { - return { - 'icon': icon, - 'value': value, - }; + return {'icon': icon, 'value': value, 'description': description}; } factory SceneOperationalValue.fromMap(Map map) { return SceneOperationalValue( icon: map['icon'] ?? '', - value: map['value'] ?? '', + value: map['value'], + description: map['description'], ); } @@ -137,7 +139,7 @@ class SceneOperationalValue { @override String toString() => - 'StaticFunctionOperationHelper(icon: $icon, value: $value)'; + 'StaticFunctionOperationHelper(icon: $icon, value: $value, description: $description)'; @override bool operator ==(Object other) { @@ -145,9 +147,10 @@ class SceneOperationalValue { return other is SceneOperationalValue && other.icon == icon && + other.description == description && other.value == value; } @override - int get hashCode => icon.hashCode ^ value.hashCode; + int get hashCode => icon.hashCode ^ value.hashCode ^ description.hashCode; } diff --git a/lib/features/scene/repo/scene_repo.dart b/lib/features/scene/repo/scene_repo.dart deleted file mode 100644 index eaa7705..0000000 --- a/lib/features/scene/repo/scene_repo.dart +++ /dev/null @@ -1,16 +0,0 @@ -import 'package:syncrow_app/features/devices/model/device_control_model.dart'; -import 'package:syncrow_app/services/api/devices_api.dart'; - -class SceneRepo { - Future deviceControl({ - required DeviceControlModel controlModel, - required String deviceId, - }) async { - final response = await DevicesAPI.controlDevice(controlModel, deviceId); - if (response['success'] == true) { - return true; - } else { - return false; - } - } -} diff --git a/lib/features/scene/view/create_scene_view.dart b/lib/features/scene/view/create_scene_view.dart index 7bf6db9..85e29bd 100644 --- a/lib/features/scene/view/create_scene_view.dart +++ b/lib/features/scene/view/create_scene_view.dart @@ -35,7 +35,7 @@ class CreateSceneView extends StatelessWidget { Routes.sceneTasksRoute, arguments: CreateSceneEnum.tabToRun, ); - context.read().tasksList.clear(); + context.read().add(ClearTaskListEvent()); }, ), DefaultContainer( @@ -53,3 +53,5 @@ class CreateSceneView extends StatelessWidget { ); } } + + diff --git a/lib/features/scene/view/device_functions_view.dart b/lib/features/scene/view/device_functions_view.dart index 541092d..94d7f13 100644 --- a/lib/features/scene/view/device_functions_view.dart +++ b/lib/features/scene/view/device_functions_view.dart @@ -3,11 +3,11 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_app/features/devices/model/device_control_model.dart'; import 'package:syncrow_app/features/devices/model/device_model.dart'; import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.dart'; -import 'package:syncrow_app/features/scene/helper/scene_helper.dart'; +import 'package:syncrow_app/features/scene/helper/scene_operations_data_helper.dart'; import 'package:syncrow_app/features/scene/model/scene_static_function.dart'; -import 'package:syncrow_app/features/scene/widgets/alert_dialog_countdown.dart'; -import 'package:syncrow_app/features/scene/widgets/alert_dialog_functions_body.dart'; -import 'package:syncrow_app/features/scene/widgets/alert_dialog_temperature_body.dart'; +import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart'; +import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart'; +import 'package:syncrow_app/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart'; 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_scaffold.dart'; @@ -18,12 +18,13 @@ import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; -class DeviceFunctionsView extends StatelessWidget with SceneHelper { +class DeviceFunctionsView extends StatelessWidget + with SceneOperationsDataHelper { const DeviceFunctionsView({super.key}); @override Widget build(BuildContext context) { - /// this whole widget i need to revamp it later + /// this whole widget needs a refactor later /// /// static functions based on type final device = ModalRoute.of(context)?.settings.arguments as DeviceModel; diff --git a/lib/features/scene/view/scene_tasks_view.dart b/lib/features/scene/view/scene_tasks_view.dart index 5c8f1f2..8f341ca 100644 --- a/lib/features/scene/view/scene_tasks_view.dart +++ b/lib/features/scene/view/scene_tasks_view.dart @@ -1,10 +1,9 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_app/features/scene/widgets/create_scene_save_button.dart'; import 'package:syncrow_app/features/scene/widgets/if_then_containers/if_container.dart'; import 'package:syncrow_app/features/scene/widgets/if_then_containers/then_container.dart'; -import 'package:syncrow_app/features/shared_widgets/default_button.dart'; import 'package:syncrow_app/features/shared_widgets/default_scaffold.dart'; -import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; import 'package:syncrow_app/generated/assets.dart'; import 'package:syncrow_app/utils/context_extension.dart'; import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; @@ -60,18 +59,7 @@ class SceneTasksView extends StatelessWidget { left: 40, child: SizedBox( width: context.width * 0.8, - child: DefaultButton( - onPressed: () {}, - customButtonStyle: ButtonStyle( - backgroundColor: WidgetStateProperty.all( - ColorsManager.primaryColorWithOpacity, - ), - ), - child: BodyLarge( - text: 'Save', - style: context.bodyLarge.copyWith(color: Colors.white), - ), - ), + child: const CreateSceneSaveButton(), ), ) ], diff --git a/lib/features/scene/view/scene_view.dart b/lib/features/scene/view/scene_view.dart index 6b495b5..9182669 100644 --- a/lib/features/scene/view/scene_view.dart +++ b/lib/features/scene/view/scene_view.dart @@ -48,8 +48,10 @@ class SceneView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Image.asset( height: 50, @@ -85,8 +87,10 @@ class SceneView extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: + CrossAxisAlignment.start, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, children: [ Image.asset( height: 50, diff --git a/lib/features/scene/widgets/alert_dialog_countdown.dart b/lib/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart similarity index 91% rename from lib/features/scene/widgets/alert_dialog_countdown.dart rename to lib/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart index 455ae98..66102c0 100644 --- a/lib/features/scene/widgets/alert_dialog_countdown.dart +++ b/lib/features/scene/widgets/alert_dialogs/alert_dialog_countdown.dart @@ -6,7 +6,7 @@ import 'package:syncrow_app/features/scene/bloc/create_scene/create_scene_bloc.d class AlertDialogCountdown extends StatefulWidget { const AlertDialogCountdown({super.key, required this.durationValue}); - final String durationValue; + final int durationValue; @override State createState() => _AlertDialogCountdownState(); @@ -16,7 +16,7 @@ class _AlertDialogCountdownState extends State { int durationInSeconds = 0; // Convert seconds to Duration. Duration get duration => - Duration(seconds: int.tryParse(widget.durationValue) ?? 0); + Duration(seconds: widget.durationValue) ; @override Widget build(BuildContext context) { return Container( @@ -32,7 +32,7 @@ class _AlertDialogCountdownState extends State { }); context .read() - .add(SelectedValueEvent(value: newDuration.inSeconds.toString())); + .add(SelectedValueEvent(value: newDuration.inSeconds)); }, ), ); diff --git a/lib/features/scene/widgets/alert_dialog_functions_body.dart b/lib/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart similarity index 96% rename from lib/features/scene/widgets/alert_dialog_functions_body.dart rename to lib/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart index 52fa50b..07e1809 100644 --- a/lib/features/scene/widgets/alert_dialog_functions_body.dart +++ b/lib/features/scene/widgets/alert_dialogs/alert_dialog_functions_body.dart @@ -21,7 +21,7 @@ class AlertDialogFunctionsOperationsBody extends StatefulWidget { class _AlertDialogFunctionsOperationsBodyState extends State { - String? groupValue = ""; + dynamic groupValue; @override Widget build(BuildContext context) { return SingleChildScrollView( @@ -36,7 +36,7 @@ class _AlertDialogFunctionsOperationsBodyState minLeadingWidth: 15, padding: const EdgeInsets.symmetric(horizontal: 16), assetPath: operation.icon, - titleString: operation.value, + titleString: operation.description.toString(), textAlign: TextAlign.start, trailingWidget: Radio( value: operation.value, @@ -58,7 +58,7 @@ class _AlertDialogFunctionsOperationsBodyState }); context .read() - .add(SelectedValueEvent(value: groupValue!)); + .add(SelectedValueEvent(value: groupValue)); }, ); }, diff --git a/lib/features/scene/widgets/alert_dialog_temperature_body.dart b/lib/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart similarity index 94% rename from lib/features/scene/widgets/alert_dialog_temperature_body.dart rename to lib/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart index 5aeb430..c5fd1e3 100644 --- a/lib/features/scene/widgets/alert_dialog_temperature_body.dart +++ b/lib/features/scene/widgets/alert_dialogs/alert_dialog_temperature_body.dart @@ -40,7 +40,7 @@ class _AlertDialogTemperatureBodyState }); context .read() - .add(SelectedValueEvent(value: temperature.toString())); + .add(SelectedValueEvent(value: temperature * 10)); }, icon: const Icon( Icons.remove, @@ -70,7 +70,8 @@ class _AlertDialogTemperatureBodyState ], ), subtitle: BodyLarge( - text: widget.functions[widget.index].operationalValues[0].value, + text: widget.functions[widget.index].operationalValues[0].description + .toString(), textAlign: TextAlign.center, ), trailing: IconButton( @@ -82,7 +83,7 @@ class _AlertDialogTemperatureBodyState }); context .read() - .add(SelectedValueEvent(value: temperature.toString())); + .add(SelectedValueEvent(value: temperature * 10)); }, icon: const Icon( Icons.add, diff --git a/lib/features/scene/widgets/create_scene_save_button.dart b/lib/features/scene/widgets/create_scene_save_button.dart new file mode 100644 index 0000000..ab565d1 --- /dev/null +++ b/lib/features/scene/widgets/create_scene_save_button.dart @@ -0,0 +1,106 @@ +import 'package:flutter/material.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/helper/scene_logic_helper.dart'; +import 'package:syncrow_app/features/shared_widgets/default_button.dart'; +import 'package:syncrow_app/features/shared_widgets/text_widgets/body_large.dart'; +import 'package:syncrow_app/utils/context_extension.dart'; +import 'package:syncrow_app/utils/resource_manager/color_manager.dart'; + +class CreateSceneSaveButton extends StatefulWidget { + const CreateSceneSaveButton({ + super.key, + }); + + @override + State createState() => _CreateSceneSaveButtonState(); +} + +class _CreateSceneSaveButtonState extends State + with SceneLogicHelper { + late TextEditingController sceneNameController; + + @override + void initState() { + sceneNameController = TextEditingController(); + super.initState(); + } + + @override + void dispose() { + sceneNameController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return BlocConsumer( + listener: (context, state) { + if (state is CreateSceneWithTasks) { + if (state.success == true) { + context.showCustomSnackbar( + message: 'Scene created successfully', + icon: const Icon( + Icons.check, + color: Colors.green, + ), + ); + sceneNameController.text = ''; + } + } else if (state is CreateSceneError) { + context.showCustomSnackbar( + message: state.message, + icon: const Icon( + Icons.error, + color: Colors.red, + ), + ); + } + }, + builder: (context, state) { + return DefaultButton( + onPressed: () { + context.customAlertDialog( + alertBody: Padding( + padding: const EdgeInsets.only(left: 8, right: 8, bottom: 8), + child: SizedBox( + height: 40, + child: SearchBar( + controller: sceneNameController, + elevation: WidgetStateProperty.all(0), + textStyle: WidgetStateProperty.all(context.bodyMedium), + hintStyle: WidgetStateProperty.all( + context.bodyMedium.copyWith( + fontSize: 14, + color: ColorsManager.secondaryTextColor), + ), + hintText: 'Enter scene name', + backgroundColor: + WidgetStateProperty.all(ColorsManager.backgroundColor), + ), + ), + ), + title: 'Scene Name', + onConfirm: () { + if (sceneNameController.text.isNotEmpty) { + final tasks = context.read().tasksList; + handleSaveButtonPress(context, sceneNameController, tasks); + } + }, + ); + }, + customButtonStyle: ButtonStyle( + backgroundColor: WidgetStateProperty.all( + ColorsManager.primaryColorWithOpacity, + ), + ), + isLoading: state is CreateSceneLoading, + child: BodyLarge( + text: 'Save', + style: context.bodyLarge.copyWith(color: Colors.white), + ), + ); + }, + ); + } +} diff --git a/lib/services/api/api_links_endpoints.dart b/lib/services/api/api_links_endpoints.dart index ebf8c89..f8fd0a7 100644 --- a/lib/services/api/api_links_endpoints.dart +++ b/lib/services/api/api_links_endpoints.dart @@ -9,7 +9,8 @@ abstract class ApiEndpoints { static const String deleteUser = '$baseUrl/authentication/user/delete/{id}'; static const String sendOtp = '$baseUrl/authentication/user/send-otp'; static const String verifyOtp = '$baseUrl/authentication/user/verify-otp'; - static const String forgetPassword = '$baseUrl/authentication/user/forget-password'; + static const String forgetPassword = + '$baseUrl/authentication/user/forget-password'; ////////////////////////////////////// Spaces /////////////////////////////////////// @@ -19,10 +20,12 @@ abstract class ApiEndpoints { static const String addCommunityToUser = '$baseUrl/community/user'; //GET static const String communityByUuid = '$baseUrl/community/{communityUuid}'; - static const String communityChild = '$baseUrl/community/child/{communityUuid}'; + static const String communityChild = + '$baseUrl/community/child/{communityUuid}'; static const String communityUser = '$baseUrl/community/user/{userUuid}'; //PUT - static const String renameCommunity = '$baseUrl/community/rename/{communityUuid}'; + static const String renameCommunity = + '$baseUrl/community/rename/{communityUuid}'; ///Building Module //POST @@ -31,10 +34,12 @@ abstract class ApiEndpoints { //GET static const String buildingByUuid = '$baseUrl/building/{buildingUuid}'; static const String buildingChild = '$baseUrl/building/child/{buildingUuid}'; - static const String buildingParent = '$baseUrl/building/parent/{buildingUuid}'; + static const String buildingParent = + '$baseUrl/building/parent/{buildingUuid}'; static const String buildingUser = '$baseUrl/building/user/{userUuid}'; //PUT - static const String renameBuilding = '$baseUrl/building/rename/{buildingUuid}'; + static const String renameBuilding = + '$baseUrl/building/rename/{buildingUuid}'; ///Floor Module //POST @@ -57,7 +62,8 @@ abstract class ApiEndpoints { static const String unitChild = '$baseUrl/unit/child/'; static const String unitParent = '$baseUrl/unit/parent/{unitUuid}'; static const String unitUser = '$baseUrl/unit/user/'; - static const String invitationCode = '$baseUrl/unit/{unitUuid}/invitation-code'; + static const String invitationCode = + '$baseUrl/unit/{unitUuid}/invitation-code'; static const String verifyInvitationCode = '$baseUrl/unit/user/verify-code'; //PUT @@ -95,8 +101,10 @@ abstract class ApiEndpoints { //GET static const String deviceByRoom = '$baseUrl/device/room'; static const String deviceByUuid = '$baseUrl/device/{deviceUuid}'; - static const String deviceFunctions = '$baseUrl/device/{deviceUuid}/functions'; - static const String deviceFunctionsStatus = '$baseUrl/device/{deviceUuid}/functions/status'; + static const String deviceFunctions = + '$baseUrl/device/{deviceUuid}/functions'; + static const String deviceFunctionsStatus = + '$baseUrl/device/{deviceUuid}/functions/status'; ///Device Permission Module //POST @@ -104,7 +112,26 @@ abstract class ApiEndpoints { //GET static const String devicePermissionList = '$baseUrl/device-permission/list'; //PUT - static const String editDevicePermission = '$baseUrl/device-permission/edit/{userId}'; + static const String editDevicePermission = + '$baseUrl/device-permission/edit/{userId}'; static const String assignDeviceToRoom = '$baseUrl/device/room'; + + /// Scene API //////////////////// + /// POST + static const String createScene = '$baseUrl/scene/tap-to-run'; + static const String triggerScene = + '$baseUrl/scene/tap-to-run/trigger/{sceneId}'; + + /// GET + static const String getUnitScenes = '$baseUrl/scene/tap-to-run/{unitUuid}'; + + static const String getScene = '$baseUrl/scene/tap-to-run/details/{sceneId}'; + + /// PUT + static const String updateScene = '$baseUrl/scene/{sceneId}'; + + /// DELETE + static const String deleteScene = + '$baseUrl/scene/tap-to-run/{sceneUuid}/{sceneId}'; } diff --git a/lib/services/api/scene_api.dart b/lib/services/api/scene_api.dart new file mode 100644 index 0000000..1bf7b34 --- /dev/null +++ b/lib/services/api/scene_api.dart @@ -0,0 +1,57 @@ +import 'package:syncrow_app/features/scene/model/create_scene_model.dart'; +import 'package:syncrow_app/features/scene/model/scene_model.dart'; +import 'package:syncrow_app/services/api/api_links_endpoints.dart'; +import 'package:syncrow_app/services/api/http_service.dart'; + +class SceneApi { + static final HTTPService _httpService = HTTPService(); + + static Future> createScene( + CreateSceneModel createSceneModel) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.createScene, + body: createSceneModel.toJson(), + showServerMessage: false, + expectedResponseModel: (json) { + return json; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future> getScenesByUnitId(String unitId) async { + try { + final response = await _httpService.get( + path: ApiEndpoints.getUnitScenes.replaceAll('{unitUuid}', unitId), + showServerMessage: false, + expectedResponseModel: (json) { + List scenes = []; + for (var scene in json) { + scenes.add(SceneModel.fromJson(scene)); + } + return scenes; + }, + ); + return response; + } catch (e) { + rethrow; + } + } + + static Future triggerScene(String sceneId) async { + try { + final response = await _httpService.post( + path: ApiEndpoints.triggerScene.replaceAll('{sceneUuid}', sceneId), + showServerMessage: false, + expectedResponseModel: (json) => json['success'], + ); + return response; + } catch (e) { + rethrow; + } + } +} diff --git a/lib/utils/context_extension.dart b/lib/utils/context_extension.dart index df6143a..6a84c60 100644 --- a/lib/utils/context_extension.dart +++ b/lib/utils/context_extension.dart @@ -47,6 +47,19 @@ extension ContextExtension on BuildContext { ); } + void showCustomSnackbar({required String message, Widget? icon}) { + ScaffoldMessenger.of(this).showSnackBar( + SnackBar( + content: Row( + children: [ + Expanded(child: Text(message)), + icon ?? const SizedBox.shrink(), + ], + ), + ), + ); + } + void customAlertDialog( {required Widget alertBody, required String title,