diff --git a/assets/icons/main_door.svg b/assets/icons/main_door.svg new file mode 100644 index 00000000..5f378012 --- /dev/null +++ b/assets/icons/main_door.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/icons/main_door_notifi.svg b/assets/icons/main_door_notifi.svg new file mode 100644 index 00000000..34d44e1c --- /dev/null +++ b/assets/icons/main_door_notifi.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/assets/icons/main_door_reports.svg b/assets/icons/main_door_reports.svg new file mode 100644 index 00000000..f0eb413a --- /dev/null +++ b/assets/icons/main_door_reports.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 2872ee6d..5065ec31 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 @@ -11,6 +11,7 @@ import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batc import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_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'; 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/three_gang_switch/view/living_room_batch_controls.dart'; @@ -55,7 +56,11 @@ mixin RouteControlsBasedCode { case 'AC': return AcDeviceControlsView(device: device); case 'WH': - return WaterHeaterDeviceControl(device: device,); + return WaterHeaterDeviceControl( + device: device, + ); + case 'DS': + return MainDoorSensorControlView(device: device); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart new file mode 100644 index 00000000..bb72f1d3 --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart @@ -0,0 +1,139 @@ +import 'dart:async'; +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/main_door_sensor/bloc/main_door_sensor_event.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart'; +import 'package:syncrow_web/services/devices_mang_api.dart'; + +class MainDoorSensorBloc + extends Bloc { + MainDoorSensorBloc() : super(MainDoorSensorInitial()) { + on(_onFetchDeviceStatus); + on(_onControl); + on(_onFetchBatchStatus); + on(_fetchReports); + } + + late MainDoorSensorStatusModel deviceStatus; + Timer? _timer; + + FutureOr _onFetchDeviceStatus(MainDoorSensorFetchDeviceEvent event, + Emitter emit) async { + emit(MainDoorSensorLoadingState()); + try { + final status = await DevicesManagementApi() + .getDeviceStatus(event.deviceId) + .then((value) => value.status); + + deviceStatus = MainDoorSensorStatusModel.fromJson(event.deviceId, status); + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + } catch (e) { + emit(MainDoorSensorFailedState(error: e.toString())); + } + } + + FutureOr _onControl( + MainDoorSensorControl event, Emitter emit) async { + final oldValue = _getValueByCode(event.code); + + _updateLocalValue(event.code, event.value); + + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + + await _runDebounce( + deviceId: event.deviceId, + code: event.code, + value: event.value, + oldValue: oldValue, + emit: emit, + ); + } + + Future _runDebounce({ + required String deviceId, + required String code, + required bool value, + required bool oldValue, + required Emitter emit, + }) async { + if (_timer != null) { + _timer!.cancel(); + } + + _timer = Timer(const Duration(milliseconds: 500), () async { + try { + final status = await DevicesManagementApi() + .deviceControl(deviceId, Status(code: code, value: value)); + + if (!status) { + _revertValueAndEmit(deviceId, code, oldValue, emit); + } + + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + } catch (e) { + emit(MainDoorSensorFailedState(error: e.toString())); + } + }); + } + + void _revertValueAndEmit(String deviceId, String code, bool oldValue, + Emitter emit) { + _updateLocalValue(code, oldValue); + emit(MainDoorSensorDeviceStatusLoaded(deviceStatus)); + } + + void _updateLocalValue(String code, bool value) { + switch (code) { + case 'doorcontact_state': + deviceStatus = deviceStatus.copyWith(doorContactState: value); + break; + default: + break; + } + } + + // Retrieve the current value by code + bool _getValueByCode(String code) { + switch (code) { + case 'doorcontact_state': + return deviceStatus.doorContactState; + default: + return false; + } + } + + // Fetch batch status for multiple devices (if needed) + FutureOr _onFetchBatchStatus(MainDoorSensorFetchBatchEvent event, + Emitter emit) async { + emit(MainDoorSensorLoadingState()); + try { + // final batchStatus = + // await DevicesManagementApi().getBatchDeviceStatus(event.deviceIds); + // Assuming you need to update multiple devices status here + // You might need a list or map of MainDoorSensorStatusModel for batch processing + // emit(MainDoorSensorBatchStatusLoaded(batchStatus)); + } catch (e) { + emit(MainDoorSensorBatchFailedState(error: e.toString())); + } + } + + // Fetch reports related to the main door sensor + FutureOr _fetchReports(MainDoorSensorReportsEvent event, + Emitter emit) async { + emit(MainDoorSensorLoadingState()); + try { + final reports = await DevicesManagementApi.getDeviceReports( + event.deviceId, event.code); + emit(MainDoorSensorReportLoaded(reports)); + } catch (e) { + emit(MainDoorSensorFailedState(error: e.toString())); + } + } + + @override + Future close() { + _timer?.cancel(); + return super.close(); + } +} diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart new file mode 100644 index 00000000..4db304b0 --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart @@ -0,0 +1,55 @@ +import 'package:equatable/equatable.dart'; + +class MainDoorSensorEvent extends Equatable { + @override + List get props => []; +} + +class MainDoorSensorFetchDeviceEvent extends MainDoorSensorEvent { + final String deviceId; + + MainDoorSensorFetchDeviceEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class MainDoorSensorFetchBatchEvent extends MainDoorSensorEvent { + final String deviceId; + + MainDoorSensorFetchBatchEvent(this.deviceId); + + @override + List get props => [deviceId]; +} + +class MainDoorSensorControl extends MainDoorSensorEvent { + final String deviceId; + final String code; + final bool value; + + MainDoorSensorControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} + +class MainDoorSensorBatchControl extends MainDoorSensorEvent { + final List deviceId; + final String code; + final bool value; + + MainDoorSensorBatchControl( + {required this.deviceId, required this.code, required this.value}); + + @override + List get props => [deviceId, code, value]; +} + +class MainDoorSensorReportsEvent extends MainDoorSensorEvent { + final String deviceId; + final String code; + + MainDoorSensorReportsEvent({required this.deviceId, required this.code}); +} diff --git a/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart new file mode 100644 index 00000000..d482245b --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart @@ -0,0 +1,68 @@ +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_reports.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.dart'; + +class MainDoorSensorState extends Equatable { + @override + List get props => []; +} + +class MainDoorSensorInitial extends MainDoorSensorState {} + +class MainDoorSensorLoadingState extends MainDoorSensorState {} + +class MainDoorSensorDeviceStatusLoaded extends MainDoorSensorState { + final MainDoorSensorStatusModel status; + + MainDoorSensorDeviceStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class MainDoorSensorFailedState extends MainDoorSensorState { + final String error; + + MainDoorSensorFailedState({required this.error}); + + @override + List get props => [error]; +} + +class MainDoorSensorBatchFailedState extends MainDoorSensorState { + final String error; + + MainDoorSensorBatchFailedState({required this.error}); + + @override + List get props => [error]; +} + +class MainDoorSensorBatchStatusLoaded extends MainDoorSensorState { + final List status; + + MainDoorSensorBatchStatusLoaded(this.status); + + @override + List get props => [status]; +} + +class MainDoorSensorReportLoaded extends MainDoorSensorState { + final DeviceReport deviceReport; + + MainDoorSensorReportLoaded(this.deviceReport); + + @override + List get props => [deviceReport]; +} + +class MainDoorSensorReportsLoadingState extends MainDoorSensorState {} + +class MainDoorSensorReportsFailedState extends MainDoorSensorState { + final String error; + + MainDoorSensorReportsFailedState({required this.error}); + + @override + List get props => [error]; +} diff --git a/lib/pages/device_managment/main_door_sensor/models/main_door_status_model.dart b/lib/pages/device_managment/main_door_sensor/models/main_door_status_model.dart new file mode 100644 index 00000000..52dda7a3 --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/models/main_door_status_model.dart @@ -0,0 +1,47 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class MainDoorSensorStatusModel { + final String uuid; + final bool doorContactState; + final int batteryPercentage; + + MainDoorSensorStatusModel({ + required this.uuid, + required this.doorContactState, + required this.batteryPercentage, + }); + + factory MainDoorSensorStatusModel.fromJson(String id, List jsonList) { + late bool doorContactState = false; + late int batteryPercentage = 0; + + for (var status in jsonList) { + switch (status.code) { + case 'doorcontact_state': + doorContactState = status.value ?? false; + break; + case 'battery_percentage': + batteryPercentage = status.value ?? 0; + break; + } + } + + return MainDoorSensorStatusModel( + uuid: id, + doorContactState: doorContactState, + batteryPercentage: batteryPercentage, + ); + } + + MainDoorSensorStatusModel copyWith({ + String? uuid, + bool? doorContactState, + int? batteryPercentage, + }) { + return MainDoorSensorStatusModel( + uuid: uuid ?? this.uuid, + doorContactState: doorContactState ?? this.doorContactState, + batteryPercentage: batteryPercentage ?? this.batteryPercentage, + ); + } +} diff --git a/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart new file mode 100644 index 00000000..a4ac0777 --- /dev/null +++ b/lib/pages/device_managment/main_door_sensor/view/main_door_control_view.dart @@ -0,0 +1,148 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_event.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/bloc/main_door_sensor_state.dart'; +import 'package:syncrow_web/pages/device_managment/main_door_sensor/models/main_door_status_model.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'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class MainDoorSensorControlView extends StatelessWidget + with HelperResponsiveLayout { + const MainDoorSensorControlView({super.key, required this.device}); + + final AllDevicesModel device; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => MainDoorSensorBloc() + ..add(MainDoorSensorFetchDeviceEvent(device.uuid!)), + child: BlocBuilder( + builder: (context, state) { + if (state is MainDoorSensorLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is MainDoorSensorDeviceStatusLoaded) { + return _buildStatusControls(context, state.status); + } else if (state is MainDoorSensorFailedState || + state is MainDoorSensorBatchFailedState) { + return const Center(child: Text('Error fetching status')); + } else { + return const Center(child: CircularProgressIndicator()); + } + }, + )); + } + + Widget _buildStatusControls( + BuildContext context, MainDoorSensorStatusModel status) { + final isExtraLarge = isExtraLargeScreenSize(context); + final isLarge = isLargeScreenSize(context); + final isMedium = isMediumScreenSize(context); + return GridView( + padding: const EdgeInsets.symmetric(horizontal: 50), + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: isLarge || isExtraLarge + ? 3 + : isMedium + ? 2 + : 1, + mainAxisExtent: 140, + crossAxisSpacing: 12, + mainAxisSpacing: 12, + ), + children: [ + IconNameStatusContainer( + name: 'Open', + icon: Assets.mainDoor, + onTap: () {}, + status: status.doorContactState, + textColor: ColorsManager.red, + paddingAmount: 8, + ), + IconNameStatusContainer( + name: 'Open/Close\n Record', + icon: Assets.mainDoorReports, + onTap: () {}, + status: false, + textColor: ColorsManager.blackColor, + ), + IconNameStatusContainer( + name: 'Notifications\n Settings', + icon: Assets.mainDoorNotifi, + onTap: () {}, + status: false, + textColor: ColorsManager.blackColor, + ), + ], + ); + } +} + +class IconNameStatusContainer extends StatelessWidget { + const IconNameStatusContainer({ + super.key, + required this.name, + required this.icon, + required this.onTap, + required this.status, + required this.textColor, + this.paddingAmount = 12, + }); + + final String name; + final String icon; + final GestureTapCallback onTap; + final bool status; + final Color textColor; + final double? paddingAmount; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: DeviceControlsContainer( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: 60, + height: 60, + decoration: const BoxDecoration( + shape: BoxShape.circle, + color: ColorsManager.whiteColors, + ), + margin: const EdgeInsets.symmetric(horizontal: 4), + padding: EdgeInsets.all(paddingAmount ?? 12), + child: ClipOval( + child: SvgPicture.asset( + icon, + fit: BoxFit.fill, + ), + ), + ), + const Spacer(), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 6), + child: Text( + name, + textAlign: TextAlign.start, + style: context.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.w400, + color: textColor, + ), + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index da578100..3c22e031 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -148,4 +148,10 @@ class Assets { static const String firmware = 'assets/icons/firmware.svg'; //assets/images/scheduling.svg static const String scheduling = 'assets/images/scheduling.svg'; + //assets/icons/main_door_notifi.svg + static const String mainDoorNotifi = 'assets/icons/main_door_notifi.svg'; + //assets/icons/main_door_reports.svg + static const String mainDoorReports = 'assets/icons/main_door_reports.svg'; + //assets/icons/main_door.svg + static const String mainDoor = 'assets/icons/main_door.svg'; }