diff --git a/assets/icons/close_curtain.svg b/assets/icons/close_curtain.svg
new file mode 100644
index 00000000..53f9e03b
--- /dev/null
+++ b/assets/icons/close_curtain.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/open_curtain.svg b/assets/icons/open_curtain.svg
new file mode 100644
index 00000000..715773a5
--- /dev/null
+++ b/assets/icons/open_curtain.svg
@@ -0,0 +1,8 @@
+
diff --git a/assets/icons/pause_curtain.svg b/assets/icons/pause_curtain.svg
new file mode 100644
index 00000000..8f90ea4f
--- /dev/null
+++ b/assets/icons/pause_curtain.svg
@@ -0,0 +1,6 @@
+
diff --git a/assets/icons/reverse_arrows.svg b/assets/icons/reverse_arrows.svg
new file mode 100644
index 00000000..fe119c39
--- /dev/null
+++ b/assets/icons/reverse_arrows.svg
@@ -0,0 +1,10 @@
+
diff --git a/lib/pages/device_managment/ac/bloc/ac_bloc.dart b/lib/pages/device_managment/ac/bloc/ac_bloc.dart
index 2ac26b41..85e119f1 100644
--- a/lib/pages/device_managment/ac/bloc/ac_bloc.dart
+++ b/lib/pages/device_managment/ac/bloc/ac_bloc.dart
@@ -84,9 +84,14 @@ class AcBloc extends Bloc {
statusList.add(Status(code: element['code'], value: element['value']));
});
+
+ deviceStatus =
+ AcStatusModel.fromJson(usersMap['productUuid'], statusList);
+
deviceStatus = AcStatusModel.fromJson(usersMap['productUuid'], statusList);
print('Device status updated: ${deviceStatus.acSwitch}');
+
if (!isClosed) {
add(AcStatusUpdated(deviceStatus));
}
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 5586a310..08bca73c 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
@@ -7,6 +7,8 @@ import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_s
import 'package:syncrow_web/pages/device_managment/ceiling_sensor/view/ceiling_sensor_controls.dart';
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_status_view.dart';
import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart';
+import 'package:syncrow_web/pages/device_managment/curtain_module/view/curtain_module_batch.dart';
+import 'package:syncrow_web/pages/device_managment/curtain_module/view/curtain_module_items.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/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart';
@@ -18,6 +20,7 @@ import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_view.dar
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_control_view.dart';
import 'package:syncrow_web/pages/device_managment/main_door_sensor/view/main_door_sensor_batch_view.dart';
import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_batch_control_view.dart';
+import 'package:syncrow_web/pages/device_managment/one_g_glass_switch/view/one_gang_glass_switch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_batch_control.dart';
import 'package:syncrow_web/pages/device_managment/one_gang_switch/view/wall_light_device_control.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/view/power_clamp_batch_control_view.dart';
@@ -39,8 +42,6 @@ import 'package:syncrow_web/pages/device_managment/water_heater/view/water_heate
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_batch_control_view.dart';
import 'package:syncrow_web/pages/device_managment/water_leak/view/water_leak_control_view.dart';
-import '../../one_g_glass_switch/view/one_gang_glass_switch_control_view.dart';
-
mixin RouteControlsBasedCode {
Widget routeControlsWidgets({required AllDevicesModel device}) {
switch (device.productType) {
@@ -84,6 +85,10 @@ mixin RouteControlsBasedCode {
return CurtainStatusControlsView(
deviceId: device.uuid!,
);
+ case 'CUR_2':
+ return CurtainModuleItems(
+ deviceId: device.uuid!,
+ );
case 'AC':
return AcDeviceControlsView(device: device);
case 'WH':
@@ -107,7 +112,7 @@ mixin RouteControlsBasedCode {
case 'SOS':
return SosDeviceControlsView(device: device);
- case 'NCPS':
+ case 'NCPS':
return FlushMountedPresenceSensorControlView(device: device);
default:
return const SizedBox();
@@ -132,76 +137,140 @@ 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 'CUR_2':
+ return CurtainModuleBatchView(
+ devicesIds: devices
+ .where((e) => e.productType == 'CUR_2')
+ .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(),
+ deviceIds: devices
+ .where((e) => e.productType == 'GD')
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'WL':
return WaterLeakBatchControlView(
- deviceIds: devices.where((e) => (e.productType == 'WL')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => e.productType == 'WL')
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'PC':
return PowerClampBatchControlView(
- deviceIds: devices.where((e) => (e.productType == 'PC')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => e.productType == 'PC')
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'SOS':
return SOSBatchControlView(
- deviceIds: devices.where((e) => (e.productType == 'SOS')).map((e) => e.uuid!).toList(),
+ deviceIds: devices
+ .where((e) => e.productType == 'SOS')
+ .map((e) => e.uuid!)
+ .toList(),
);
case 'NCPS':
return FlushMountedPresenceSensorBatchControlView(
- devicesIds: devices.where((e) => (e.productType == 'NCPS')).map((e) => e.uuid!).toList(),
+ devicesIds: devices
+ .where((e) => e.productType == 'NCPS')
+ .map((e) => e.uuid!)
+ .toList(),
);
default:
return const SizedBox();
diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart
new file mode 100644
index 00000000..b40d7ea6
--- /dev/null
+++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart
@@ -0,0 +1,379 @@
+import 'dart:async';
+import 'package:bloc/bloc.dart';
+import 'package:equatable/equatable.dart';
+import 'package:firebase_database/firebase_database.dart';
+import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
+import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart';
+import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart';
+import 'package:syncrow_web/services/batch_control_devices_service.dart';
+import 'package:syncrow_web/services/control_device_service.dart';
+import 'package:syncrow_web/services/devices_mang_api.dart';
+
+part 'curtain_module_event.dart';
+part 'curtain_module_state.dart';
+
+class CurtainModuleBloc extends Bloc {
+ final ControlDeviceService controlDeviceService;
+ final BatchControlDevicesService batchControlDevicesService;
+ StreamSubscription? _firebaseSubscription;
+
+ CurtainModuleBloc({
+ required this.controlDeviceService,
+ required this.batchControlDevicesService,
+ }) : super(CurtainModuleInitial()) {
+ on(_onFetchCurtainModuleStatusEvent);
+ on(_onSendCurtainPercentToApiEvent);
+ on(_onOpenCurtainEvent);
+ on(_onCloseCurtainEvent);
+ on(_onStopCurtainEvent);
+ on(_onChangeTimerControlEvent);
+ on(_onChageCurCalibrationEvent);
+ on(_onChangeElecMachineryModeEvent);
+ on(_onChangeControlBackEvent);
+ on(_onChangeControlBackModeEvent);
+ on(_onChangeCurtainModuleStatusEvent);
+ //batch
+ on(_onFetchCurtainModuleBatchStatus);
+ on(_onSendCurtainBatchPercentToApiEvent);
+ on(_onOpenCurtainBatchEvent);
+ on(_onCloseCurtainBatchEvent);
+ on(_onStopCurtainBatchEvent);
+ on(_onFactoryReset);
+ }
+
+ Future _onFetchCurtainModuleStatusEvent(
+ FetchCurtainModuleStatusEvent event,
+ Emitter emit,
+ ) async {
+ emit(CurtainModuleLoading());
+ final status = await DevicesManagementApi().getDeviceStatus(event.deviceId);
+ final result = Map.fromEntries(
+ status.status.map((element) => MapEntry(element.code, element.value)),
+ );
+
+ emit(CurtainModuleStatusLoaded(
+ curtainModuleStatus: CurtainModuleStatusModel.fromJson(result),
+ ));
+ Map statusMap = {};
+ final ref =
+ FirebaseDatabase.instance.ref('device-status/${event.deviceId}');
+ final stream = ref.onValue;
+
+ stream.listen((DatabaseEvent DatabaseEvent) async {
+ if (DatabaseEvent.snapshot.value == null) return;
+
+ Map usersMap =
+ DatabaseEvent.snapshot.value as Map;
+
+ List statusList = [];
+
+ usersMap['status'].forEach((element) {
+ statusList.add(Status(code: element['code'], value: element['value']));
+ });
+
+ statusMap = {
+ for (final element in statusList) element.code: element.value,
+ };
+ if (!isClosed) {
+ add(
+ ChangeCurtainModuleStatusEvent(
+ deviceId: event.deviceId,
+ status: CurtainModuleStatusModel.fromJson(statusMap),
+ ),
+ );
+ }
+ });
+ }
+
+ Future _onChangeCurtainModuleStatusEvent(
+ ChangeCurtainModuleStatusEvent event,
+ Emitter emit,
+ ) async {
+ emit(CurtainModuleLoading());
+ emit(CurtainModuleStatusLoaded(curtainModuleStatus: event.status));
+ }
+
+ Future _onSendCurtainPercentToApiEvent(
+ SendCurtainPercentToApiEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: event.status,
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to send control command: $e'));
+ }
+ }
+
+ Future _onOpenCurtainEvent(
+ OpenCurtainEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: Status(code: 'control', value: 'open'),
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to open curtain: $e'));
+ }
+ }
+
+ Future _onCloseCurtainEvent(
+ CloseCurtainEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: Status(code: 'control', value: 'close'),
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to close curtain: $e'));
+ }
+ }
+
+ Future _onStopCurtainEvent(
+ StopCurtainEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: Status(code: 'control', value: 'stop'),
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to stop curtain: $e'));
+ }
+ }
+
+ Future _onChangeTimerControlEvent(
+ ChangeTimerControlEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ if (event.timControl < 10 || event.timControl > 120) {
+ emit(const CurtainModuleError(
+ message: 'Timer control value must be between 10 and 120'));
+ return;
+ }
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: Status(
+ code: 'tr_timecon',
+ value: event.timControl,
+ ),
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to change timer control: $e'));
+ }
+ }
+
+ Future _onChageCurCalibrationEvent(
+ CurCalibrationEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: Status(code: 'cur_calibration', value: 'start'),
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to start calibration: $e'));
+ }
+ }
+
+ Future _onChangeElecMachineryModeEvent(
+ ChangeElecMachineryModeEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: Status(
+ code: 'elec_machinery_mode',
+ value: event.elecMachineryMode,
+ ),
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to change mode: $e'));
+ }
+ }
+
+ Future _onChangeControlBackEvent(
+ ChangeControlBackEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: Status(
+ code: 'control_back',
+ value: event.controlBack,
+ ),
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to change control back: $e'));
+ }
+ }
+
+ Future _onChangeControlBackModeEvent(
+ ChangeControlBackModeEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await controlDeviceService.controlDevice(
+ deviceUuid: event.deviceId,
+ status: Status(
+ code: 'control_back_mode',
+ value: event.controlBackMode,
+ ),
+ );
+ } catch (e) {
+ emit(CurtainModuleError(
+ message: 'Failed to change control back mode: $e'));
+ }
+ }
+
+ FutureOr _onFetchCurtainModuleBatchStatus(
+ CurtainModuleFetchBatchStatusEvent event,
+ Emitter emit,
+ ) async {
+ emit(CurtainModuleLoading());
+ try {
+ final status =
+ await DevicesManagementApi().getBatchStatus(event.devicesIds);
+
+ final result = Map.fromEntries(
+ status.status.map((element) => MapEntry(element.code, element.value)),
+ );
+
+ emit(CurtainModuleStatusLoaded(
+ curtainModuleStatus: CurtainModuleStatusModel.fromJson(result),
+ ));
+
+ Map statusMap = {};
+ final ref = FirebaseDatabase.instance
+ .ref('device-status/${event.devicesIds.first}');
+ final stream = ref.onValue;
+
+ stream.listen((DatabaseEvent DatabaseEvent) async {
+ if (DatabaseEvent.snapshot.value == null) return;
+
+ Map usersMap =
+ DatabaseEvent.snapshot.value as Map;
+
+ List statusList = [];
+
+ usersMap['status'].forEach((element) {
+ statusList
+ .add(Status(code: element['code'], value: element['value']));
+ });
+
+ statusMap = {
+ for (final element in statusList) element.code: element.value,
+ };
+ if (!isClosed) {
+ add(
+ ChangeCurtainModuleStatusEvent(
+ deviceId: event.devicesIds.first,
+ status: CurtainModuleStatusModel.fromJson(statusMap),
+ ),
+ );
+ }
+ });
+ } catch (e) {
+ emit(CurtainModuleError(message: e.toString()));
+ }
+ }
+
+ Future _onSendCurtainBatchPercentToApiEvent(
+ SendCurtainBatchPercentToApiEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await batchControlDevicesService.batchControlDevices(
+ uuids: event.devicesId,
+ code: event.status.code,
+ value: event.status.value,
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to send control command: $e'));
+ }
+ }
+
+ Future _onOpenCurtainBatchEvent(
+ OpenCurtainBatchEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await batchControlDevicesService.batchControlDevices(
+ uuids: event.devicesId,
+ code: 'control',
+ value: 'open',
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to open curtain: $e'));
+ }
+ }
+
+ Future _onCloseCurtainBatchEvent(
+ CloseCurtainBatchEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await batchControlDevicesService.batchControlDevices(
+ uuids: event.devicesId,
+ code: 'control',
+ value: 'close',
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to close curtain: $e'));
+ }
+ }
+
+ Future _onStopCurtainBatchEvent(
+ StopCurtainBatchEvent event,
+ Emitter emit,
+ ) async {
+ try {
+ await batchControlDevicesService.batchControlDevices(
+ uuids: event.devicesId,
+ code: 'control',
+ value: 'stop',
+ );
+ } catch (e) {
+ emit(CurtainModuleError(message: 'Failed to stop curtain: $e'));
+ }
+ }
+
+ Future _onFactoryReset(
+ CurtainModuleFactoryReset event,
+ Emitter emit,
+ ) async {
+ emit(CurtainModuleLoading());
+ try {
+ final response = await DevicesManagementApi().factoryReset(
+ event.factoryReset,
+ event.deviceId,
+ );
+ if (!response) {
+ emit(const CurtainModuleError(message: 'Failed'));
+ } else {
+ add(
+ FetchCurtainModuleStatusEvent(deviceId: event.deviceId),
+ );
+ }
+ } catch (e) {
+ emit(CurtainModuleError(message: e.toString()));
+ }
+ }
+
+ @override
+ Future close() async {
+ await _firebaseSubscription?.cancel();
+ return super.close();
+ }
+}
diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart
new file mode 100644
index 00000000..4eec030d
--- /dev/null
+++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_event.dart
@@ -0,0 +1,193 @@
+part of 'curtain_module_bloc.dart';
+
+sealed class CurtainModuleEvent extends Equatable {
+ const CurtainModuleEvent();
+
+ @override
+ List