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 get props => []; +} + +class FetchCurtainModuleStatusEvent extends CurtainModuleEvent { + final String deviceId; + const FetchCurtainModuleStatusEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class SendCurtainPercentToApiEvent extends CurtainModuleEvent { + final String deviceId; + final Status status; + + const SendCurtainPercentToApiEvent({ + required this.deviceId, + required this.status, + }); + + @override + List get props => [deviceId, status]; +} + +class OpenCurtainEvent extends CurtainModuleEvent { + final String deviceId; + + const OpenCurtainEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class CloseCurtainEvent extends CurtainModuleEvent { + final String deviceId; + + const CloseCurtainEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class StopCurtainEvent extends CurtainModuleEvent { + final String deviceId; + + const StopCurtainEvent({required this.deviceId}); + + @override + List get props => [deviceId]; +} + +class ChangeTimerControlEvent extends CurtainModuleEvent { + final String deviceId; + final int timControl; + + const ChangeTimerControlEvent({ + required this.deviceId, + required this.timControl, + }); + + @override + List get props => [deviceId, timControl]; +} + +class CurCalibrationEvent extends CurtainModuleEvent { + final String deviceId; + + const CurCalibrationEvent({ + required this.deviceId, + }); + + @override + List get props => [deviceId]; +} + +class ChangeElecMachineryModeEvent extends CurtainModuleEvent { + final String deviceId; + final String elecMachineryMode; + + const ChangeElecMachineryModeEvent({ + required this.deviceId, + required this.elecMachineryMode, + }); + + @override + List get props => [deviceId, elecMachineryMode]; +} + +class ChangeControlBackEvent extends CurtainModuleEvent { + final String deviceId; + final String controlBack; + + const ChangeControlBackEvent({ + required this.deviceId, + required this.controlBack, + }); + + @override + List get props => [deviceId, controlBack]; +} + +class ChangeControlBackModeEvent extends CurtainModuleEvent { + final String deviceId; + final String controlBackMode; + + const ChangeControlBackModeEvent({ + required this.deviceId, + required this.controlBackMode, + }); + + @override + List get props => [deviceId, controlBackMode]; +} + +class ChangeCurtainModuleStatusEvent extends CurtainModuleEvent { + final String deviceId; + final CurtainModuleStatusModel status; + + const ChangeCurtainModuleStatusEvent({ + required this.deviceId, + required this.status, + }); + + @override + List get props => [deviceId, status]; +} + +///batch +class CurtainModuleFetchBatchStatusEvent extends CurtainModuleEvent { + final List devicesIds; + + const CurtainModuleFetchBatchStatusEvent(this.devicesIds); + + @override + List get props => [devicesIds]; +} + +class SendCurtainBatchPercentToApiEvent extends CurtainModuleEvent { + final List devicesId; + final Status status; + + const SendCurtainBatchPercentToApiEvent({ + required this.devicesId, + required this.status, + }); + + @override + List get props => [devicesId, status]; +} + +class OpenCurtainBatchEvent extends CurtainModuleEvent { + final List devicesId; + + const OpenCurtainBatchEvent({required this.devicesId}); + + @override + List get props => [devicesId]; +} + +class CloseCurtainBatchEvent extends CurtainModuleEvent { + final List devicesId; + + const CloseCurtainBatchEvent({required this.devicesId}); + + @override + List get props => [devicesId]; +} + +class StopCurtainBatchEvent extends CurtainModuleEvent { + final List devicesId; + + const StopCurtainBatchEvent({required this.devicesId}); + + @override + List get props => [devicesId]; +} + +class CurtainModuleFactoryReset extends CurtainModuleEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + const CurtainModuleFactoryReset( + {required this.deviceId, required this.factoryReset}); + + @override + List get props => [deviceId, factoryReset]; +} diff --git a/lib/pages/device_managment/curtain_module/bloc/curtain_module_state.dart b/lib/pages/device_managment/curtain_module/bloc/curtain_module_state.dart new file mode 100644 index 00000000..02ef9279 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/bloc/curtain_module_state.dart @@ -0,0 +1,37 @@ +part of 'curtain_module_bloc.dart'; + +sealed class CurtainModuleState extends Equatable { + const CurtainModuleState(); + + @override + List get props => []; +} + +class CurtainModuleInitial extends CurtainModuleState {} + +class CurtainModuleLoading extends CurtainModuleState {} + +class CurtainModuleError extends CurtainModuleState { + final String message; + const CurtainModuleError({required this.message}); + + @override + List get props => [message]; +} + +class CurtainModuleStatusLoaded extends CurtainModuleState { + final CurtainModuleStatusModel curtainModuleStatus; + + const CurtainModuleStatusLoaded({required this.curtainModuleStatus}); + + @override + List get props => [curtainModuleStatus]; +} +class CurtainModuleStatusUpdated extends CurtainModuleState { + final CurtainModuleStatusModel curtainModuleStatus; + + const CurtainModuleStatusUpdated({required this.curtainModuleStatus}); + + @override + List get props => [curtainModuleStatus]; +} diff --git a/lib/pages/device_managment/curtain_module/models/curtain_module_model.dart b/lib/pages/device_managment/curtain_module/models/curtain_module_model.dart new file mode 100644 index 00000000..0b6d23fb --- /dev/null +++ b/lib/pages/device_managment/curtain_module/models/curtain_module_model.dart @@ -0,0 +1,53 @@ + +enum CurtainModuleControl { + open, + close, + stop, +} + +// enum CurtainControlBackMode { +// foward, +// backward, +// } + +class CurtainModuleStatusModel { + CurtainModuleControl control; + int percentControl; + String curCalibration; + // CurtainControlBackMode controlBackmode; + int trTimeControl; + String elecMachineryMode; + String controlBack; + CurtainModuleStatusModel({ + required this.control, + required this.percentControl, + required this.curCalibration, + // required this.controlBackmode, + required this.trTimeControl, + required this.controlBack, + required this.elecMachineryMode, + }); + factory CurtainModuleStatusModel.zero() => CurtainModuleStatusModel( + control: CurtainModuleControl.stop, + percentControl: 0, + // controlBackmode: CurtainControlBackMode.foward, + curCalibration: '', + trTimeControl: 0, + controlBack: '', + elecMachineryMode: '', + ); + + factory CurtainModuleStatusModel.fromJson(Map json) { + return CurtainModuleStatusModel( + control: CurtainModuleControl.values.firstWhere( + (e) => e.toString() == json['control'] as String, + orElse: () => CurtainModuleControl.stop, + ), + percentControl: json['percent_control'] as int? ?? 0, + curCalibration: json['cur_calibration'] as String? ?? '', + trTimeControl: json['tr_timecon'] as int? ?? 0, + elecMachineryMode: json['elec_machinery_mode'] as String? ?? '', + controlBack: json['control_back'] as String? ?? '', + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart new file mode 100644 index 00000000..bd28cd8a --- /dev/null +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_batch.dart @@ -0,0 +1,80 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/factory_reset_model.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class CurtainModuleBatchView extends StatelessWidget { + final List devicesIds; + const CurtainModuleBatchView({ + super.key, + required this.devicesIds, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => CurtainModuleBloc( + controlDeviceService: RemoteControlDeviceService(), + batchControlDevicesService: RemoteBatchControlDevicesService()) + ..add(CurtainModuleFetchBatchStatusEvent(devicesIds)), + child: _buildStatusControls(context), + ); + } + + Widget _buildStatusControls(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ControlCurtainMovementWidget( + devicesId: devicesIds, + ), + const SizedBox( + height: 10, + ), + SizedBox( + height: 120, + // width: 350, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + // Expanded( + // child: + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + CurtainModuleFactoryReset( + deviceId: devicesIds.first, + factoryReset: + FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, + ), + // ), + // Expanded( + // child: IconNameStatusContainer( + // isFullIcon: false, + // name: 'Firmware Update', + // icon: Assets.firmware, + // onTap: () {}, + // status: false, + // textColor: ColorsManager.blackColor, + // ), + // ) + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart new file mode 100644 index 00000000..82c812ce --- /dev/null +++ b/lib/pages/device_managment/curtain_module/view/curtain_module_items.dart @@ -0,0 +1,120 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart'; +import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_control_button.dart'; +import 'package:syncrow_web/pages/device_managment/shared/icon_name_status_container.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class CurtainModuleItems extends StatelessWidget with HelperResponsiveLayout { + final String deviceId; + const CurtainModuleItems({ + super.key, + required this.deviceId, + }); + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => CurtainModuleBloc( + controlDeviceService: RemoteControlDeviceService(), + batchControlDevicesService: RemoteBatchControlDevicesService()) + ..add(FetchCurtainModuleStatusEvent(deviceId: deviceId)), + child: BlocBuilder( + builder: (context, state) { + return _buildStatusControls(context); + }, + ), + ); + } + + Widget _buildStatusControls(BuildContext context) { + return Padding( + padding: const EdgeInsets.only(left: 30), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ControlCurtainMovementWidget( + devicesId: [deviceId], + ), + const SizedBox( + height: 10, + ), + SizedBox( + height: 140, + width: 350, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: ScheduleControlButton( + onTap: () { + showDialog( + context: context, + builder: (ctx) => BlocProvider.value( + value: + BlocProvider.of(context), + child: BuildScheduleView( + deviceUuid: deviceId, + category: 'CUR_2', + code: 'control', + + ), + )); + }, + mainText: '', + subtitle: 'Scheduling', + iconPath: Assets.scheduling, + ), + ), + const SizedBox( + width: 10, + ), + Expanded( + child: BlocBuilder( + builder: (context, state) { + if (state is CurtainModuleLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (state is CurtainModuleStatusLoaded) { + return IconNameStatusContainer( + isFullIcon: false, + name: 'Preferences', + icon: Assets.preferences, + onTap: () => showDialog( + context: context, + builder: (_) => BlocProvider.value( + value: context.read(), + child: CurtainModulePrefrencesDialog( + curtainModuleBloc: + context.watch(), + deviceId: deviceId, + curtainModuleStatusModel: + state.curtainModuleStatus, + ), + ), + ), + status: false, + textColor: ColorsManager.blackColor, + ); + } else { + return const SizedBox(); + } + }, + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart new file mode 100644 index 00000000..54107420 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; + +class AccurteCalibratingDialog extends StatelessWidget { + final String deviceId; + final BuildContext parentContext; + const AccurteCalibratingDialog({ + super.key, + required this.deviceId, + required this.parentContext, + }); + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: AccurateDialogWidget( + title: 'Calibrating', + body: const NormalTextBodyForDialog( + title: '', + step1: + '1. Click Close Button to make the Curtain run to Full Close and Position.', + step2: '2. click Next to complete the Calibration.', + ), + leftOnTap: () => Navigator.of(parentContext).pop(), + rightOnTap: () { + parentContext.read().add( + CurCalibrationEvent( + deviceId: deviceId, + ), + ); + Navigator.of(parentContext).pop(); + showDialog( + context: parentContext, + builder: (_) => CalibrateCompletedDialog( + parentContext: parentContext, + deviceId: deviceId, + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart new file mode 100644 index 00000000..a9d1b010 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibrating_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; + +class AccurateCalibrationDialog extends StatelessWidget { + final String deviceId; + final BuildContext parentContext; + const AccurateCalibrationDialog({ + super.key, + required this.deviceId, + required this.parentContext, + }); + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: AccurateDialogWidget( + title: 'Accurate Calibration', + body: const NormalTextBodyForDialog( + title: 'Prepare Calibration:', + step1: '1. Run The Curtain to the Fully Open Position,and pause.', + step2: '2. click Next to Start accurate calibration.', + ), + leftOnTap: () => Navigator.of(parentContext).pop(), + rightOnTap: () { + Navigator.of(parentContext).pop(); + showDialog( + context: parentContext, + builder: (_) => AccurteCalibratingDialog( + deviceId: deviceId, + parentContext: parentContext, + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart new file mode 100644 index 00000000..5be376ae --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart @@ -0,0 +1,97 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class AccurateDialogWidget extends StatelessWidget { + final String title; + final Widget body; + final void Function() leftOnTap; + final void Function() rightOnTap; + const AccurateDialogWidget({ + super.key, + required this.title, + required this.body, + required this.leftOnTap, + required this.rightOnTap, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + height: 300, + width: 400, + child: Column( + children: [ + Padding( + padding: const EdgeInsets.all(10), + child: Text( + title, + style: const TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorsManager.blueColor, + ), + ), + ), + const SizedBox(height: 5), + const Divider( + indent: 10, + endIndent: 10, + ), + Padding( + padding: const EdgeInsets.all(10), + child: body, + ), + const SizedBox(height: 20), + const Spacer(), + const Divider(), + Row( + children: [ + Expanded( + child: InkWell( + onTap: leftOnTap, + child: Container( + height: 60, + alignment: Alignment.center, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + ), + ), + ), + child: const Text( + 'Cancel', + style: TextStyle(color: ColorsManager.grayBorder), + ), + ), + ), + ), + Expanded( + child: InkWell( + onTap: rightOnTap, + child: Container( + height: 60, + alignment: Alignment.center, + decoration: const BoxDecoration( + border: Border( + right: BorderSide( + color: ColorsManager.grayBorder, + ), + ), + ), + child: const Text( + 'Next', + style: TextStyle( + color: ColorsManager.blueColor, + ), + ), + ), + ), + ) + ], + ) + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart new file mode 100644 index 00000000..9b2b5ea9 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CalibrateCompletedDialog extends StatelessWidget { + final BuildContext parentContext; + final String deviceId; + const CalibrateCompletedDialog({ + super.key, + required this.parentContext, + required this.deviceId, + }); + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: SizedBox( + height: 250, + width: 400, + child: Column( + children: [ + const Padding( + padding: EdgeInsets.all(10), + child: Text( + 'Calibration Completed', + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.bold, + color: ColorsManager.blueColor, + ), + ), + ), + const SizedBox(height: 5), + const Divider( + indent: 10, + endIndent: 10, + ), + const Icon( + Icons.check_circle, + size: 100, + color: ColorsManager.blueColor, + ), + const Spacer(), + const Divider( + indent: 10, + endIndent: 10, + ), + InkWell( + onTap: () { + parentContext.read().add( + FetchCurtainModuleStatusEvent( + deviceId: deviceId, + ), + ); + Navigator.of(parentContext).pop(); + Navigator.of(parentContext).pop(); + }, + child: Container( + height: 40, + width: double.infinity, + alignment: Alignment.center, + child: const Text( + 'Close', + style: TextStyle( + color: ColorsManager.grayBorder, + ), + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart b/lib/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart new file mode 100644 index 00000000..8c2ff81c --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class CurtainActionWidget extends StatelessWidget { + final String icon; + final void Function() onTap; + const CurtainActionWidget({ + super.key, + required this.icon, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: onTap, + child: ClipOval( + child: Container( + height: 60, + width: 60, + padding: const EdgeInsets.all(8), + color: ColorsManager.whiteColors, + child: ClipOval( + child: Container( + height: 60, + width: 60, + padding: const EdgeInsets.all(8), + color: ColorsManager.graysColor, + child: SvgPicture.asset( + icon, + width: 35, + height: 35, + fit: BoxFit.contain, + ), + ), + ), + )), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart b/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart new file mode 100644 index 00000000..e98ff11d --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/curtain_movment_widget.dart @@ -0,0 +1,219 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/curtain_action_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; + +class ControlCurtainMovementWidget extends StatelessWidget { + final List devicesId; + const ControlCurtainMovementWidget({ + super.key, + required this.devicesId, + }); + + @override + Widget build(BuildContext context) { + return SizedBox( + width: 550, + child: DeviceControlsContainer( + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CurtainActionWidget( + icon: Assets.openCurtain, + onTap: () { + if (devicesId.length == 1) { + context.read().add( + OpenCurtainEvent(deviceId: devicesId.first), + ); + } else { + context.read().add( + OpenCurtainBatchEvent(devicesId: devicesId), + ); + } + }, + ), + const SizedBox( + width: 30, + ), + CurtainActionWidget( + icon: Assets.pauseCurtain, + onTap: () { + if (devicesId.length == 1) { + context.read().add( + StopCurtainEvent(deviceId: devicesId.first), + ); + } else { + context.read().add( + StopCurtainBatchEvent(devicesId: devicesId), + ); + } + }, + ), + const SizedBox( + width: 30, + ), + CurtainActionWidget( + icon: Assets.closeCurtain, + onTap: () { + if (devicesId.length == 1) { + context.read().add( + CloseCurtainEvent(deviceId: devicesId.first), + ); + } else { + context.read().add( + CloseCurtainBatchEvent(devicesId: devicesId), + ); + } + }, + ), + BlocBuilder( + builder: (context, state) { + if (state is CurtainModuleError) { + return Center( + child: Text( + state.message, + style: const TextStyle( + color: ColorsManager.minBlueDot, + fontSize: 16, + ), + ), + ); + } else if (state is CurtainModuleLoading) { + return const Center( + child: CircularProgressIndicator( + color: ColorsManager.minBlueDot, + ), + ); + } else if (state is CurtainModuleInitial) { + return const Center( + child: Text( + 'No data available', + style: TextStyle( + color: ColorsManager.minBlueDot, + fontSize: 16, + ), + ), + ); + } else if (state is CurtainModuleStatusLoaded) { + return CurtainSliderWidget( + status: state.curtainModuleStatus, + devicesId: devicesId, + ); + } else { + return const Center( + child: Text( + 'Unknown state', + style: TextStyle( + color: ColorsManager.minBlueDot, + fontSize: 16, + ), + ), + ); + } + }, + ) + ], + ), + ), + ); + } +} + +class CurtainSliderWidget extends StatefulWidget { + final CurtainModuleStatusModel status; + final List devicesId; + + const CurtainSliderWidget({ + super.key, + required this.status, + required this.devicesId, + }); + + @override + State createState() => _CurtainSliderWidgetState(); +} + +class _CurtainSliderWidgetState extends State { + double? _localValue; // For temporary drag state + + @override + Widget build(BuildContext context) { + // If user is dragging, use local value. Otherwise, use Firebase-synced state + final double currentSliderValue = + _localValue ?? widget.status.percentControl / 100; + + return Column( + children: [ + Text( + '${(currentSliderValue * 100).round()}%', + style: const TextStyle( + color: ColorsManager.minBlueDot, + fontSize: 25, + fontWeight: FontWeight.bold, + ), + ), + Slider( + value: currentSliderValue, + min: 0, + max: 1, + divisions: 10, // 10% step + activeColor: ColorsManager.minBlueDot, + thumbColor: ColorsManager.primaryColor, + inactiveColor: ColorsManager.whiteColors, + + // Start dragging — use local control + onChangeStart: (_) { + setState(() { + _localValue = currentSliderValue; + }); + }, + + // While dragging — update temporary value + onChanged: (value) { + final steppedValue = (value * 10).roundToDouble() / 10; + setState(() { + _localValue = steppedValue; + }); + }, + + // On release — send API and return to Firebase-controlled state + onChangeEnd: (value) { + final int targetPercent = (value * 100).round(); + + if (widget.devicesId.length == 1) { + context.read().add( + SendCurtainPercentToApiEvent( + deviceId: widget.devicesId.first, + status: Status( + code: 'percent_control', + value: targetPercent, + ), + ), + ); + } else { + context.read().add( + SendCurtainBatchPercentToApiEvent( + devicesId: widget.devicesId, + status: Status( + code: 'percent_control', + value: targetPercent, + ), + ), + ); + } + + // Revert back to Firebase-synced stream + setState(() { + _localValue = null; + }); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart new file mode 100644 index 00000000..8818cb7b --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class NormalTextBodyForDialog extends StatelessWidget { + final String title; + final String step1; + final String step2; + + const NormalTextBodyForDialog({ + super.key, + required this.title, + required this.step1, + required this.step2, + }); + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: const TextStyle( + color: ColorsManager.grayColor, + ), + ), + Text( + step1, + style: const TextStyle( + color: ColorsManager.grayColor, + ), + ), + Text( + step2, + style: const TextStyle( + color: ColorsManager.grayColor, + ), + ) + ], + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart b/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart new file mode 100644 index 00000000..ea95f838 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/number_input_textfield.dart @@ -0,0 +1,27 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class NumberInputField extends StatelessWidget { + final TextEditingController controller; + + const NumberInputField({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return TextField( + controller: controller, + keyboardType: TextInputType.number, + inputFormatters: [FilteringTextInputFormatter.digitsOnly], + decoration: const InputDecoration( + border: InputBorder.none, + isDense: true, + contentPadding: EdgeInsets.zero, + ), + style: const TextStyle( + fontSize: 20, + color: ColorsManager.blackColor, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart b/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart new file mode 100644 index 00000000..81912e80 --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart @@ -0,0 +1,79 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/web_layout/default_container.dart'; + +class PrefReversCardWidget extends StatelessWidget { + final void Function() onTap; + final String title; + final String body; + const PrefReversCardWidget({ + super.key, + required this.title, + required this.body, + required this.onTap, + }); + + @override + Widget build(BuildContext context) { + return DefaultContainer( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + flex: 8, + child: Text( + title, + style: const TextStyle( + color: ColorsManager.grayBorder, + fontSize: 15, + ), + ), + ), + const SizedBox( + width: 20, + ), + Expanded( + flex: 2, + child: InkWell( + onTap: onTap, + child: Container( + padding: const EdgeInsets.all(3), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: const BorderRadius.horizontal( + left: Radius.circular(10), + right: Radius.circular(10)), + border: Border.all(color: ColorsManager.grayBorder)), + child: SvgPicture.asset( + Assets.reverseArrows, + height: 15, + ), + ), + ), + ) + ], + ), + SizedBox( + width: 100, + child: Text( + body, + style: const TextStyle( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w500, + fontSize: 18, + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart new file mode 100644 index 00000000..1e4f932c --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/prefrences_dialog.dart @@ -0,0 +1,149 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/models/curtain_module_model.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_calibration_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/pref_revers_card_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/web_layout/default_container.dart'; + +class CurtainModulePrefrencesDialog extends StatelessWidget { + final CurtainModuleStatusModel curtainModuleStatusModel; + final String deviceId; + final CurtainModuleBloc curtainModuleBloc; + const CurtainModulePrefrencesDialog({ + super.key, + required this.curtainModuleStatusModel, + required this.deviceId, + required this.curtainModuleBloc, + }); + + @override + Widget build(_) { + return AlertDialog( + backgroundColor: ColorsManager.CircleImageBackground, + contentPadding: const EdgeInsets.all(30), + title: const Center( + child: Text( + 'Preferences', + style: TextStyle( + color: ColorsManager.blueColor, + fontSize: 24, + fontWeight: FontWeight.bold, + ), + )), + content: BlocBuilder( + bloc: curtainModuleBloc, + builder: (context, state) { + if (state is CurtainModuleLoading) { + return const Center( + child: CircularProgressIndicator(), + ); + } else if (state is CurtainModuleStatusLoaded) { + return SizedBox( + height: 300, + width: 400, + child: GridView( + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 2, + childAspectRatio: 1.5, + mainAxisSpacing: 10, + crossAxisSpacing: 10, + ), + children: [ + PrefReversCardWidget( + title: state.curtainModuleStatus.controlBack, + body: 'Motor Steering', + onTap: () { + context.read().add( + ChangeControlBackEvent( + deviceId: deviceId, + controlBack: + state.curtainModuleStatus.controlBack == + 'forward' + ? 'back' + : 'forward', + ), + ); + }, + ), + PrefReversCardWidget( + title: formatDeviceType( + state.curtainModuleStatus.elecMachineryMode), + body: 'Motor Mode', + onTap: () => context.read().add( + ChangeElecMachineryModeEvent( + deviceId: deviceId, + elecMachineryMode: + state.curtainModuleStatus.elecMachineryMode == + 'dry_contact' + ? 'strong_power' + : 'dry_contact', + ), + ), + ), + DefaultContainer( + padding: const EdgeInsets.all(12), + child: InkWell( + onTap: () => showDialog( + context: context, + builder: (_) => AccurateCalibrationDialog( + deviceId: deviceId, + parentContext: context, + ), + ), + child: const Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text('Accurte Calibration', + style: TextStyle( + fontSize: 18, + color: ColorsManager.blackColor, + )), + ], + ), + ), + ), + DefaultContainer( + padding: const EdgeInsets.all(12), + child: InkWell( + onTap: () => showDialog( + context: context, + builder: (_) => QuickCalibrationDialog( + timControl: state.curtainModuleStatus.trTimeControl, + deviceId: deviceId, + parentContext: context), + ), + child: const Column( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Text('Quick Calibration', + style: TextStyle( + fontSize: 18, + color: ColorsManager.blackColor, + )), + ], + ), + ), + ), + ], + ), + ); + } else { + return const SizedBox(); + } + }, + ), + ); + } + + String formatDeviceType(String raw) { + return raw + .split('_') + .map((word) => word.isNotEmpty + ? '${word[0].toUpperCase()}${word.substring(1)}' + : '') + .join(' '); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart new file mode 100644 index 00000000..0b86c96e --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/bloc/curtain_module_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/calibrate_completed_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/number_input_textfield.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; + +class QuickCalibratingDialog extends StatefulWidget { + final int timControl; + final String deviceId; + final BuildContext parentContext; + const QuickCalibratingDialog({ + super.key, + required this.timControl, + required this.deviceId, + required this.parentContext, + }); + + @override + State createState() => _QuickCalibratingDialogState(); +} + +class _QuickCalibratingDialogState extends State { + late TextEditingController _controller; + String? _errorText; + + void _onRightTap() { + final value = int.tryParse(_controller.text); + + if (value == null || value < 10 || value > 120) { + setState(() { + _errorText = 'Number should be between 10 and 120'; + }); + return; + } + + setState(() { + _errorText = null; + }); + widget.parentContext.read().add( + ChangeTimerControlEvent( + deviceId: widget.deviceId, + timControl: value, + ), + ); + Navigator.of(widget.parentContext).pop(); + showDialog( + context: widget.parentContext, + builder: (_) => CalibrateCompletedDialog( + parentContext: widget.parentContext, + deviceId: widget.deviceId, + ), + ); + } + + @override + void initState() { + _controller = TextEditingController(text: widget.timControl.toString()); + super.initState(); + } + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: AccurateDialogWidget( + title: 'Calibrating', + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const Text( + '1. please Enter the Travel Time:', + style: TextStyle(color: ColorsManager.grayBorder), + ), + const SizedBox(height: 10), + Container( + width: 150, + height: 40, + padding: const EdgeInsets.all(5), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Expanded( + child: NumberInputField(controller: _controller), + ), + const Expanded( + child: Text( + 'seconds', + style: TextStyle( + fontSize: 15, + color: ColorsManager.blueColor, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + if (_errorText != null) + Padding( + padding: const EdgeInsets.only(top: 8.0), + child: Text( + _errorText!, + style: const TextStyle( + color: ColorsManager.red, + fontSize: 14, + ), + ), + ), + ], + ), + leftOnTap: () => Navigator.of(widget.parentContext).pop(), + rightOnTap: _onRightTap, + ), + ); + } +} diff --git a/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart b/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart new file mode 100644 index 00000000..803d904f --- /dev/null +++ b/lib/pages/device_managment/curtain_module/widgets/quick_calibration_dialog.dart @@ -0,0 +1,44 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/accurate_dialog_widget.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/normal_text_body_for_dialog.dart'; +import 'package:syncrow_web/pages/device_managment/curtain_module/widgets/quick_calibrating_dialog.dart'; + +class QuickCalibrationDialog extends StatelessWidget { + final int timControl; + final String deviceId; + final BuildContext parentContext; + const QuickCalibrationDialog({ + super.key, + required this.timControl, + required this.deviceId, + required this.parentContext, + }); + + @override + Widget build(_) { + return AlertDialog( + contentPadding: EdgeInsets.zero, + content: AccurateDialogWidget( + title: 'Quick Calibration', + body: const NormalTextBodyForDialog( + title: 'Prepare Calibration:', + step1: + '1. Confirm that the curtain is in the fully closed and suspended state.', + step2: '2. click Next to Start calibration.', + ), + leftOnTap: () => Navigator.of(parentContext).pop(), + rightOnTap: () { + Navigator.of(parentContext).pop(); + showDialog( + context: parentContext, + builder: (_) => QuickCalibratingDialog( + timControl: timControl, + deviceId: deviceId, + parentContext: parentContext, + ), + ); + }, + ), + ); + } +} diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart index fbf7ae64..0ec55e39 100644 --- a/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_bloc.dart @@ -265,7 +265,7 @@ class ScheduleBloc extends Bloc { category: event.category, deviceId: deviceId, time: getTimeStampWithoutSeconds(dateTime).toString(), - code: event.category, + code: event.code ?? event.category, value: event.functionOn, days: event.selectedDays); if (success) { diff --git a/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart b/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart index 0b9ec581..a28b8757 100644 --- a/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart +++ b/lib/pages/device_managment/schedule_device/bloc/schedule_event.dart @@ -70,17 +70,19 @@ class ScheduleAddEvent extends ScheduleEvent { final String category; final String time; final List selectedDays; - final bool functionOn; + final dynamic functionOn; + final String? code; const ScheduleAddEvent({ required this.category, required this.time, required this.selectedDays, required this.functionOn, + required this.code, }); @override - List get props => [category, time, selectedDays, functionOn]; + List get props => [category, time, selectedDays, functionOn, code]; } class ScheduleEditEvent extends ScheduleEvent { diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart index 47534d37..c511b8bd 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedual_view.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/bloc/schedule_bloc.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_button.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/count_down_inching_view.dart'; @@ -9,13 +10,19 @@ import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widg import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_buttons.dart'; import 'package:syncrow_web/pages/device_managment/schedule_device/schedule_widgets/schedule_mode_selector.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart'; +import 'package:syncrow_web/pages/device_managment/water_heater/models/schedule_entry.dart'; import 'package:syncrow_web/pages/device_managment/water_heater/models/water_heater_status_model.dart'; class BuildScheduleView extends StatelessWidget { - const BuildScheduleView( - {super.key, required this.deviceUuid, required this.category}); + const BuildScheduleView({ + super.key, + required this.deviceUuid, + required this.category, + this.code, + }); final String deviceUuid; final String category; + final String? code; @override Widget build(BuildContext context) { @@ -57,13 +64,21 @@ class BuildScheduleView extends StatelessWidget { final entry = await ScheduleDialogHelper .showAddScheduleDialog( context, - schedule: null, + schedule: ScheduleEntry( + category: category, + time: '', + function: Status( + code: code.toString(), value: null), + days: [], + ), isEdit: false, + code: code, ); if (entry != null) { context.read().add( ScheduleAddEvent( - category: entry.category, + category: category, + code: entry.function.code, time: entry.time, functionOn: entry.function.value, selectedDays: entry.days, @@ -74,7 +89,7 @@ class BuildScheduleView extends StatelessWidget { ), if (state.scheduleMode == ScheduleModes.countdown || state.scheduleMode == ScheduleModes.inching) - CountdownInchingView( + CountdownInchingView( deviceId: deviceUuid, ), const SizedBox(height: 20), diff --git a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart index 98ae0515..b23e48df 100644 --- a/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart +++ b/lib/pages/device_managment/schedule_device/schedule_widgets/schedule_table.dart @@ -162,11 +162,18 @@ class _ScheduleTableView extends StatelessWidget { child: GestureDetector( behavior: HitTestBehavior.opaque, onTap: () { + bool temp; + if (schedule.category == 'CUR_2') { + temp = schedule.function.value == 'open' ? true : false; + } else { + temp = schedule.function.value as bool; + } context.read().add( ScheduleUpdateEntryEvent( category: schedule.category, scheduleId: schedule.scheduleId, - functionOn: schedule.function.value, + functionOn: temp, + // schedule.function.value, enable: !schedule.enable, ), ); @@ -188,7 +195,10 @@ class _ScheduleTableView extends StatelessWidget { child: Text(_getSelectedDays( ScheduleModel.parseSelectedDays(schedule.days)))), Center(child: Text(formatIsoStringToTime(schedule.time, context))), - Center(child: Text(schedule.function.value ? 'On' : 'Off')), + schedule.category == 'CUR_2' + ? Center( + child: Text(schedule.function.value == true ? 'open' : 'close')) + : Center(child: Text(schedule.function.value ? 'On' : 'Off')), Center( child: Wrap( runAlignment: WrapAlignment.center, diff --git a/lib/pages/device_managment/shared/device_batch_control_dialog.dart b/lib/pages/device_managment/shared/device_batch_control_dialog.dart index f2dc68f5..c7ea6c71 100644 --- a/lib/pages/device_managment/shared/device_batch_control_dialog.dart +++ b/lib/pages/device_managment/shared/device_batch_control_dialog.dart @@ -4,7 +4,8 @@ import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_mo import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; -class DeviceBatchControlDialog extends StatelessWidget with RouteControlsBasedCode { +class DeviceBatchControlDialog extends StatelessWidget + with RouteControlsBasedCode { final List devices; const DeviceBatchControlDialog({super.key, required this.devices}); @@ -18,7 +19,7 @@ class DeviceBatchControlDialog extends StatelessWidget with RouteControlsBasedCo borderRadius: BorderRadius.circular(20), ), child: SizedBox( - width: devices.length < 2 ? 500 : 800, + width: devices.length < 2 ? 600 : 800, // height: context.screenHeight * 0.7, child: SingleChildScrollView( child: Padding( diff --git a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart index ae7feac9..389eac3f 100644 --- a/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart +++ b/lib/pages/device_managment/water_heater/helper/add_schedule_dialog_helper.dart @@ -17,6 +17,7 @@ class ScheduleDialogHelper { BuildContext context, { ScheduleEntry? schedule, bool isEdit = false, + String? code, }) { final initialTime = schedule != null ? _convertStringToTimeOfDay(schedule.time) @@ -96,7 +97,8 @@ class ScheduleDialogHelper { setState(() => selectedDays[i] = v); }), const SizedBox(height: 16), - _buildFunctionSwitch(ctx, functionOn!, (v) { + _buildFunctionSwitch(schedule!.category, ctx, functionOn!, + (v) { setState(() => functionOn = v); }), ], @@ -115,10 +117,21 @@ class ScheduleDialogHelper { width: 100, child: ElevatedButton( onPressed: () { + dynamic temp; + if (schedule?.category == 'CUR_2') { + temp = functionOn! ? 'open' : 'close'; + } else { + temp = functionOn; + } + print(temp); final entry = ScheduleEntry( category: schedule?.category ?? 'switch_1', time: _formatTimeOfDayToISO(selectedTime), - function: Status(code: 'switch_1', value: functionOn), + function: Status( + code: code ?? 'switch_1', + value: temp, + // functionOn, + ), days: _convertSelectedDaysToStrings(selectedDays), scheduleId: schedule?.scheduleId, ); @@ -185,7 +198,7 @@ class ScheduleDialogHelper { } static Widget _buildFunctionSwitch( - BuildContext ctx, bool isOn, Function(bool) onChanged) { + String categor, BuildContext ctx, bool isOn, Function(bool) onChanged) { return Row( children: [ Text( @@ -199,14 +212,14 @@ class ScheduleDialogHelper { groupValue: isOn, onChanged: (val) => onChanged(true), ), - const Text('On'), + Text(categor == 'CUR_2' ? 'open' : 'On'), const SizedBox(width: 10), Radio( value: false, groupValue: isOn, onChanged: (val) => onChanged(false), ), - const Text('Off'), + Text(categor == 'CUR_2' ? 'close' : 'Off'), ], ); } diff --git a/lib/pages/visitor_password/model/device_model.dart b/lib/pages/visitor_password/model/device_model.dart index f9711eed..75d00350 100644 --- a/lib/pages/visitor_password/model/device_model.dart +++ b/lib/pages/visitor_password/model/device_model.dart @@ -80,6 +80,10 @@ class DeviceModel { tempIcon = Assets.openedDoor; } else if (type == DeviceType.WaterLeak) { tempIcon = Assets.waterLeakNormal; + } else if (type == DeviceType.Curtain2) { + tempIcon = Assets.curtainIcon; + } else if (type == DeviceType.Curtain) { + tempIcon = Assets.curtainIcon; } else { tempIcon = Assets.blackLogo; } diff --git a/lib/services/batch_control_devices_service.dart b/lib/services/batch_control_devices_service.dart index f78cdef4..16542c8c 100644 --- a/lib/services/batch_control_devices_service.dart +++ b/lib/services/batch_control_devices_service.dart @@ -11,7 +11,8 @@ abstract interface class BatchControlDevicesService { }); } -final class RemoteBatchControlDevicesService implements BatchControlDevicesService { +final class RemoteBatchControlDevicesService + implements BatchControlDevicesService { @override Future batchControlDevices({ required List uuids, diff --git a/lib/services/devices_mang_api.dart b/lib/services/devices_mang_api.dart index a6b4a278..dd54cfef 100644 --- a/lib/services/devices_mang_api.dart +++ b/lib/services/devices_mang_api.dart @@ -391,7 +391,7 @@ class DevicesManagementApi { required String deviceId, required String time, required String code, - required bool value, + required dynamic value, required List days, }) async { final response = await HTTPService().post( diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 6a0ef799..8979c446 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -125,6 +125,10 @@ class Assets { static const String ac = 'assets/icons/AC.svg'; //assets/icons/Curtain.svg static const String curtain = 'assets/icons/Curtain.svg'; + static const String openCurtain = 'assets/icons/open_curtain.svg'; + static const String pauseCurtain = 'assets/icons/pause_curtain.svg'; + static const String closeCurtain = 'assets/icons/close_curtain.svg'; + static const String reverseArrows = 'assets/icons/reverse_arrows.svg'; //assets/icons/doorLock.svg static const String doorLock = 'assets/icons/doorLock.svg'; //assets/icons/Gateway.svg diff --git a/lib/utils/enum/device_types.dart b/lib/utils/enum/device_types.dart index 9bfd322f..947e63aa 100644 --- a/lib/utils/enum/device_types.dart +++ b/lib/utils/enum/device_types.dart @@ -3,6 +3,7 @@ enum DeviceType { LightBulb, DoorLock, Curtain, + Curtain2, Blind, OneGang, TwoGang, @@ -44,6 +45,7 @@ enum DeviceType { Map devicesTypesMap = { "AC": DeviceType.AC, + "CUR_2": DeviceType.Curtain2, "GW": DeviceType.Gateway, "CPS": DeviceType.CeilingSensor, "DL": DeviceType.DoorLock,