diff --git a/assets/icons/closed_door.svg b/assets/icons/closed_door.svg
new file mode 100644
index 00000000..9cbf40dc
--- /dev/null
+++ b/assets/icons/closed_door.svg
@@ -0,0 +1,135 @@
+
diff --git a/assets/icons/door_delay.svg b/assets/icons/door_delay.svg
new file mode 100644
index 00000000..49dbbaef
--- /dev/null
+++ b/assets/icons/door_delay.svg
@@ -0,0 +1,29 @@
+
diff --git a/assets/icons/opened_door.svg b/assets/icons/opened_door.svg
new file mode 100644
index 00000000..386a66f1
--- /dev/null
+++ b/assets/icons/opened_door.svg
@@ -0,0 +1,104 @@
+
diff --git a/assets/icons/records.svg b/assets/icons/records.svg
new file mode 100644
index 00000000..9e316afd
--- /dev/null
+++ b/assets/icons/records.svg
@@ -0,0 +1,18 @@
+
diff --git a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart
index eca980c6..f9429bd6 100644
--- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart
+++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart
@@ -9,6 +9,8 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart';
+import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart';
+import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart';
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart';
@@ -82,6 +84,10 @@ mixin RouteControlsBasedCode {
);
case 'DS':
return MainDoorSensorControlView(device: device);
+ case 'GD':
+ return GarageDoorControlView(
+ deviceId: device.uuid!,
+ );
default:
return const SizedBox();
}
@@ -105,56 +111,105 @@ mixin RouteControlsBasedCode {
switch (devices.first.productType) {
case '1G':
return WallLightBatchControlView(
- deviceIds: devices.where((e) => (e.productType == '1G')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => (e.productType == '1G'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case '2G':
return TwoGangBatchControlView(
- deviceIds: devices.where((e) => (e.productType == '2G')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => (e.productType == '2G'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case '3G':
return LivingRoomBatchControlsView(
- deviceIds: devices.where((e) => (e.productType == '3G')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => (e.productType == '3G'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case '1GT':
return OneGangGlassSwitchBatchControlView(
- deviceIds: devices.where((e) => (e.productType == '1GT')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => (e.productType == '1GT'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case '2GT':
return TwoGangGlassSwitchBatchControlView(
- deviceIds: devices.where((e) => (e.productType == '2GT')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => (e.productType == '2GT'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case '3GT':
return ThreeGangGlassSwitchBatchControlView(
- deviceIds: devices.where((e) => (e.productType == '3GT')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => (e.productType == '3GT'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'GW':
return GatewayBatchControlView(
- gatewayIds: devices.where((e) => (e.productType == 'GW')).map((e) => e.uuid!).toList(),
+ gatewayIds: devices
+ .where((e) => (e.productType == 'GW'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'DL':
return DoorLockBatchControlView(
- devicesIds: devices.where((e) => (e.productType == 'DL')).map((e) => e.uuid!).toList());
+ devicesIds: devices
+ .where((e) => (e.productType == 'DL'))
+ .map((e) => e.uuid!)
+ .toList());
case 'WPS':
return WallSensorBatchControlView(
- devicesIds: devices.where((e) => (e.productType == 'WPS')).map((e) => e.uuid!).toList());
+ devicesIds: devices
+ .where((e) => (e.productType == 'WPS'))
+ .map((e) => e.uuid!)
+ .toList());
case 'CPS':
return CeilingSensorBatchControlView(
- devicesIds: devices.where((e) => (e.productType == 'CPS')).map((e) => e.uuid!).toList(),
+ devicesIds: devices
+ .where((e) => (e.productType == 'CPS'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'CUR':
return CurtainBatchStatusView(
- devicesIds: devices.where((e) => (e.productType == 'CUR')).map((e) => e.uuid!).toList(),
+ devicesIds: devices
+ .where((e) => (e.productType == 'CUR'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'AC':
return AcDeviceBatchControlView(
- devicesIds: devices.where((e) => (e.productType == 'AC')).map((e) => e.uuid!).toList());
+ devicesIds: devices
+ .where((e) => (e.productType == 'AC'))
+ .map((e) => e.uuid!)
+ .toList());
case 'WH':
return WaterHEaterBatchControlView(
- deviceIds: devices.where((e) => (e.productType == 'WH')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => (e.productType == 'WH'))
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'DS':
return MainDoorSensorBatchView(
- devicesIds: devices.where((e) => (e.productType == 'DS')).map((e) => e.uuid!).toList(),
+ devicesIds: devices
+ .where((e) => (e.productType == 'DS'))
+ .map((e) => e.uuid!)
+ .toList(),
+ );
+ case 'GD':
+ return GarageDoorBatchControlView(
+ deviceIds: devices
+ .where((e) => (e.productType == 'GD'))
+ .map((e) => e.uuid!)
+ .toList(),
);
default:
return const SizedBox();
diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart
new file mode 100644
index 00000000..69e58c65
--- /dev/null
+++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart
@@ -0,0 +1,433 @@
+import 'dart:async';
+
+import 'package:bloc/bloc.dart';
+import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart';
+import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
+import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_event.dart';
+import 'package:syncrow_web/pages/device_managment/garage_door/bloc/garage_door_state.dart';
+import 'package:syncrow_web/pages/device_managment/garage_door/models/garage_door_model.dart';
+import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart';
+import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_model.dart';
+import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart';
+import 'package:syncrow_web/services/devices_mang_api.dart';
+import 'package:syncrow_web/utils/format_date_time.dart';
+
+class GarageDoorBloc extends Bloc {
+ final String deviceId;
+ late GarageDoorStatusModel deviceStatus;
+ Timer? _timer;
+
+ GarageDoorBloc({required this.deviceId}) : super(GarageDoorInitialState()) {
+ on(_fetchGarageDoorStatus);
+ on(_garageDoorControlEvent);
+ on(_addSchedule);
+ on(_updateSchedule);
+ on(_deleteSchedule);
+ on(_fetchSchedules);
+ on(_increaseDelay);
+ on(_decreaseDelay);
+ on(_fetchRecords);
+ on(_handleUpdate);
+ on(_updateSelectedTime);
+ on(_updateSelectedDay);
+ on(_updateFunctionOn);
+ on(_initializeAddSchedule);
+ on(_backToGridView);
+ on(_onUpdateCountdownAlarm);
+ on(_onUpdateTrTimeCon);
+ on(_onBatchControl);
+ on(_onFetchBatchStatus);
+ on(_onFactoryReset);
+ }
+
+ void _fetchGarageDoorStatus(
+ GarageDoorInitialEvent event, Emitter emit) async {
+ emit(GarageDoorLoadingState());
+ try {
+ var response =
+ await DevicesManagementApi().getDeviceStatus(event.deviceId);
+ deviceStatus = GarageDoorStatusModel.fromJson(deviceId, response.status);
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ } catch (e) {
+ emit(GarageDoorErrorState(message: e.toString()));
+ }
+ }
+
+ Future _onFetchBatchStatus(GarageDoorFetchBatchStatusEvent event,
+ Emitter emit) async {
+ emit(GarageDoorLoadingState());
+ try {
+ final status =
+ await DevicesManagementApi().getBatchStatus(event.deviceIds);
+ deviceStatus =
+ GarageDoorStatusModel.fromJson(event.deviceIds.first, status.status);
+ emit(GarageDoorBatchStatusLoaded(deviceStatus));
+ } catch (e) {
+ emit(GarageDoorBatchControlError(e.toString()));
+ }
+ }
+
+ Future _addSchedule(
+ AddGarageDoorScheduleEvent event, Emitter emit) async {
+ try {
+ ScheduleEntry newSchedule = ScheduleEntry(
+ category: event.category,
+ time: formatTimeOfDayToISO(event.time),
+ function: Status(code: 'switch_1', value: event.functionOn),
+ days: ScheduleModel.convertSelectedDaysToStrings(event.selectedDays),
+ );
+ bool success =
+ await DevicesManagementApi().addScheduleRecord(newSchedule, deviceId);
+ if (success) {
+ add(FetchGarageDoorSchedulesEvent(
+ deviceId: deviceId, category: 'switch_1'));
+ } else {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+ } catch (e) {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+ }
+
+ void _onUpdateCountdownAlarm(
+ UpdateCountdownAlarmEvent event, Emitter emit) {
+ final currentState = state;
+ if (currentState is GarageDoorLoadedState) {
+ emit(currentState.copyWith(
+ status:
+ currentState.status.copyWith(countdownAlarm: event.countdownAlarm),
+ ));
+ }
+ }
+
+ void _onUpdateTrTimeCon(
+ UpdateTrTimeConEvent event, Emitter emit) {
+ final currentState = state;
+ if (currentState is GarageDoorLoadedState) {
+ emit(currentState.copyWith(
+ status: currentState.status.copyWith(trTimeCon: event.trTimeCon),
+ ));
+ }
+ }
+
+ Future _updateSchedule(UpdateGarageDoorScheduleEvent event,
+ Emitter emit) async {
+ try {
+ final updatedSchedules = deviceStatus.schedules?.map((schedule) {
+ if (schedule.scheduleId == event.scheduleId) {
+ return schedule.copyWith(
+ function: Status(code: 'switch_1', value: event.functionOn),
+ enable: event.enable,
+ );
+ }
+ return schedule;
+ }).toList();
+ bool success = await DevicesManagementApi().updateScheduleRecord(
+ enable: event.enable,
+ uuid: deviceStatus.uuid,
+ scheduleId: event.scheduleId,
+ );
+ if (success) {
+ deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ } else {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+ } catch (e) {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+ }
+
+ Future _deleteSchedule(DeleteGarageDoorScheduleEvent event,
+ Emitter emit) async {
+ try {
+ bool success = await DevicesManagementApi()
+ .deleteScheduleRecord(deviceStatus.uuid, event.scheduleId);
+ if (success) {
+ final updatedSchedules = deviceStatus.schedules
+ ?.where((schedule) => schedule.scheduleId != event.scheduleId)
+ .toList();
+ deviceStatus = deviceStatus.copyWith(schedules: updatedSchedules);
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ } else {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+ } catch (e) {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+ }
+
+ Future _fetchSchedules(FetchGarageDoorSchedulesEvent event,
+ Emitter emit) async {
+ emit(ScheduleGarageLoadingState());
+ try {
+ List schedules = await DevicesManagementApi()
+ .getDeviceSchedules(deviceStatus.uuid, event.category);
+ deviceStatus = deviceStatus.copyWith(schedules: schedules);
+ emit(
+ GarageDoorLoadedState(
+ status: deviceStatus,
+ scheduleMode: ScheduleModes.schedule,
+ ),
+ );
+ } catch (e) {
+ emit(
+ GarageDoorLoadedState(
+ status: deviceStatus,
+ scheduleMode: ScheduleModes.schedule,
+ ),
+ );
+ }
+ }
+
+ Future _updateSelectedTime(
+ UpdateSelectedTimeEvent event, Emitter emit) async {
+ final currentState = state;
+ if (currentState is GarageDoorLoadedState) {
+ emit(currentState.copyWith(selectedTime: event.selectedTime));
+ }
+ }
+
+ Future _updateSelectedDay(
+ UpdateSelectedDayEvent event, Emitter emit) async {
+ final currentState = state;
+ if (currentState is GarageDoorLoadedState) {
+ List updatedDays = List.from(currentState.selectedDays);
+ updatedDays[event.dayIndex] = event.isSelected;
+ emit(currentState.copyWith(
+ selectedDays: updatedDays, selectedTime: currentState.selectedTime));
+ }
+ }
+
+ Future _updateFunctionOn(
+ UpdateFunctionOnEvent event, Emitter emit) async {
+ final currentState = state;
+ if (currentState is GarageDoorLoadedState) {
+ emit(currentState.copyWith(
+ functionOn: event.functionOn,
+ selectedTime: currentState.selectedTime));
+ }
+ }
+
+ Future _initializeAddSchedule(
+ InitializeAddScheduleEvent event, Emitter emit) async {
+ final currentState = state;
+ if (currentState is GarageDoorLoadedState) {
+ emit(currentState.copyWith(
+ selectedTime: event.selectedTime,
+ selectedDays: event.selectedDays,
+ functionOn: event.functionOn,
+ isEditing: event.isEditing,
+ ));
+ }
+ }
+
+ Future _fetchRecords(
+ FetchGarageDoorRecordsEvent event, Emitter emit) async {
+ emit(GarageDoorReportsLoadingState());
+ try {
+ final from = DateTime.now()
+ .subtract(const Duration(days: 30))
+ .millisecondsSinceEpoch;
+ final to = DateTime.now().millisecondsSinceEpoch;
+ final DeviceReport records =
+ await DevicesManagementApi.getDeviceReportsByDate(
+ event.deviceId, 'switch_1', from.toString(), to.toString());
+ emit(GarageDoorReportsState(deviceReport: records));
+ } catch (e) {
+ emit(GarageDoorReportsFailedState(error: e.toString()));
+ }
+ }
+
+ Future _onBatchControl(
+ GarageDoorBatchControlEvent event, Emitter emit) async {
+ final oldValue = event.code == 'switch_1' ? deviceStatus.switch1 : false;
+
+ _updateLocalValue(event.code, event.value);
+ emit(GarageDoorBatchStatusLoaded(deviceStatus));
+
+ final success = await _runDeBouncer(
+ deviceId: event.deviceIds,
+ code: event.code,
+ value: event.value,
+ oldValue: oldValue,
+ emit: emit,
+ isBatch: true,
+ );
+
+ if (!success) {
+ _revertValue(event.code, oldValue, emit);
+ }
+ }
+
+ void _backToGridView(
+ BackToGarageDoorGridViewEvent event, Emitter emit) {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+
+ void _handleUpdate(
+ GarageDoorUpdatedEvent event, Emitter emit) {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+
+ Future _runDeBouncer({
+ required dynamic deviceId,
+ required String code,
+ required dynamic value,
+ required dynamic oldValue,
+ required Emitter emit,
+ required bool isBatch,
+ }) async {
+ try {
+ late bool status;
+ await Future.delayed(const Duration(milliseconds: 500));
+ if (isBatch) {
+ status = await DevicesManagementApi()
+ .deviceBatchControl(deviceId, code, value);
+ } else {
+ status = await DevicesManagementApi()
+ .deviceControl(deviceId, Status(code: code, value: value));
+ }
+
+ if (!status) {
+ _revertValue(code, oldValue, emit);
+ return false;
+ } else {
+ return true;
+ }
+ } catch (e) {
+ _revertValue(code, oldValue, emit);
+ return false;
+ }
+ }
+
+ Future _onFactoryReset(
+ GarageDoorFactoryResetEvent event, Emitter emit) async {
+ emit(GarageDoorLoadingState());
+ try {
+ final response = await DevicesManagementApi()
+ .factoryReset(event.factoryReset, event.deviceId);
+ if (!response) {
+ emit(const GarageDoorErrorState(message: 'Failed to reset device'));
+ } else {
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ }
+ } catch (e) {
+ emit(GarageDoorErrorState(message: e.toString()));
+ }
+ }
+
+ void _increaseDelay(
+ IncreaseGarageDoorDelayEvent event, Emitter emit) async {
+ // if (deviceStatus.countdown1 != 0) {
+ try {
+ deviceStatus = deviceStatus.copyWith(
+ delay: deviceStatus.delay + Duration(minutes: 10));
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ add(GarageDoorControlEvent(
+ deviceId: event.deviceId,
+ value: deviceStatus.delay.inSeconds,
+ code: 'countdown_1'));
+ } catch (e) {
+ emit(GarageDoorErrorState(message: e.toString()));
+ }
+ // }
+ }
+
+ void _decreaseDelay(
+ DecreaseGarageDoorDelayEvent event, Emitter emit) async {
+ // if (deviceStatus.countdown1 != 0) {
+ try {
+ if (deviceStatus.delay.inMinutes > 10) {
+ deviceStatus = deviceStatus.copyWith(
+ delay: deviceStatus.delay - Duration(minutes: 10));
+ }
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ add(GarageDoorControlEvent(
+ deviceId: event.deviceId,
+ value: deviceStatus.delay.inSeconds,
+ code: 'countdown_1'));
+ } catch (e) {
+ emit(GarageDoorErrorState(message: e.toString()));
+ }
+ //}
+ }
+
+ void _garageDoorControlEvent(
+ GarageDoorControlEvent event, Emitter emit) async {
+ final oldValue = event.code == 'countdown_1'
+ ? deviceStatus.countdown1
+ : deviceStatus.switch1;
+ _updateLocalValue(event.code, event.value);
+ emit(GarageDoorLoadedState(status: deviceStatus));
+ final success = await _runDeBouncer(
+ deviceId: event.deviceId,
+ code: event.code,
+ value: event.value,
+ oldValue: oldValue,
+ emit: emit,
+ isBatch: false,
+ );
+ if (!success) {
+ _revertValue(event.code, oldValue, emit);
+ }
+ }
+
+ void _revertValue(
+ String code, dynamic oldValue, Emitter emit) {
+ switch (code) {
+ case 'switch_1':
+ if (oldValue is bool) {
+ deviceStatus = deviceStatus.copyWith(switch1: oldValue);
+ }
+ break;
+ case 'countdown_1':
+ if (oldValue is int) {
+ deviceStatus = deviceStatus.copyWith(
+ countdown1: oldValue, delay: Duration(seconds: oldValue));
+ }
+ break;
+ // Add other cases if needed
+ default:
+ break;
+ }
+ if (state is GarageDoorLoadedState) {
+ final currentState = state as GarageDoorLoadedState;
+ emit(currentState.copyWith(status: deviceStatus));
+ }
+ }
+
+ void _updateLocalValue(String code, dynamic value) {
+ switch (code) {
+ case 'switch_1':
+ if (value is bool) {
+ deviceStatus = deviceStatus.copyWith(switch1: value);
+ }
+ break;
+ case 'countdown_1':
+ if (value is int) {
+ deviceStatus = deviceStatus.copyWith(
+ countdown1: value, delay: Duration(seconds: value));
+ }
+ break;
+ case 'countdown_alarm':
+ if (value is int) {
+ deviceStatus = deviceStatus.copyWith(countdownAlarm: value);
+ }
+ break;
+ case 'tr_timecon':
+ if (value is int) {
+ deviceStatus = deviceStatus.copyWith(trTimeCon: value);
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ @override
+ Future close() {
+ _timer?.cancel();
+ return super.close();
+ }
+}
diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart
new file mode 100644
index 00000000..24eb921e
--- /dev/null
+++ b/lib/pages/device_managment/garage_door/bloc/garage_door_event.dart
@@ -0,0 +1,220 @@
+// lib/pages/device_managment/garage_door/bloc/event.dart
+
+import 'package:equatable/equatable.dart';
+import 'package:flutter/material.dart';
+import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
+
+abstract class GarageDoorEvent extends Equatable {
+ const GarageDoorEvent();
+
+ @override
+ List