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 e38ac582..81405451 100644 --- a/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart +++ b/lib/pages/device_managment/all_devices/helper/route_controls_based_code.dart @@ -9,6 +9,7 @@ import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_batch_st import 'package:syncrow_web/pages/device_managment/curtain/view/curtain_status_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/door_lock/view/door_lock_control_view.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_batch_control_view.dart'; import 'package:syncrow_web/pages/device_managment/garage_door/view/garage_door_control_view.dart'; import 'package:syncrow_web/pages/device_managment/gateway/view/gateway_batch_control.dart'; @@ -104,6 +105,9 @@ mixin RouteControlsBasedCode { ); case 'SOS': return SosDeviceControlsView(device: device); + + case 'NCPS': + return FlushMountedPresenceSensorControlView(device: device); default: return const SizedBox(); } diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart new file mode 100644 index 00000000..0bc15cd2 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart @@ -0,0 +1,237 @@ +import 'dart:async'; +import 'dart:developer'; + +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_reports.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/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_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 'flush_mounted_presence_sensor_event.dart'; +part 'flush_mounted_presence_sensor_state.dart'; + +class FlushMountedPresenceSensorBloc + extends Bloc { + final String deviceId; + final ControlDeviceService controlDeviceService; + final BatchControlDevicesService batchControlDevicesService; + + late FlushMountedPresenceSensorModel deviceStatus; + FlushMountedPresenceSensorBloc({ + required this.deviceId, + required this.controlDeviceService, + required this.batchControlDevicesService, + }) : super(FlushMountedPresenceSensorInitialState()) { + on( + _onFlushMountedPresenceSensorFetchStatusEvent, + ); + on( + _onFlushMountedPresenceSensorFetchBatchStatusEvent); + on( + _onFlushMountedPresenceSensorChangeValueEvent, + ); + on( + _onFlushMountedPresenceSensorBatchControlEvent, + ); + on( + _onFlushMountedPresenceSensorGetDeviceReportsEvent); + on( + _onFlushMountedPresenceSensorShowDescriptionEvent, + ); + on( + _onFlushMountedPresenceSensorBackToGridViewEvent, + ); + on( + _onFlushMountedPresenceSensorFactoryResetEvent, + ); + } + + void _onFlushMountedPresenceSensorFetchStatusEvent( + FlushMountedPresenceSensorFetchStatusEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingInitialState()); + try { + final response = await DevicesManagementApi().getDeviceStatus(deviceId); + deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + _listenToChanges(emit, deviceId); + } catch (e) { + emit(FlushMountedPresenceSensorFailedState(error: e.toString())); + return; + } + } + + Future _onFlushMountedPresenceSensorFetchBatchStatusEvent( + FlushMountedPresenceSensorFetchBatchStatusEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingInitialState()); + try { + final response = await DevicesManagementApi().getBatchStatus(event.devicesIds); + deviceStatus = FlushMountedPresenceSensorModel.fromJson(response.status); + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + } catch (e) { + emit(FlushMountedPresenceSensorFailedState(error: e.toString())); + } + } + + Future _listenToChanges( + Emitter emit, + String deviceId, + ) async { + final ref = FirebaseDatabase.instance.ref( + 'device-status/$deviceId', + ); + + await ref.onValue.listen( + (DatabaseEvent event) async { + Map usersMap = + event.snapshot.value as Map; + List statusList = []; + + (usersMap['status'] as List?)?.forEach((element) { + statusList.add(Status(code: element['code'], value: element['value'])); + }); + + deviceStatus = FlushMountedPresenceSensorModel.fromJson(statusList); + if (!emit.isDone) { + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + } + }, + onError: (error) => log(error.toString(), name: 'FirebaseDatabaseError'), + ).asFuture(); + } + + void _onFlushMountedPresenceSensorChangeValueEvent( + FlushMountedPresenceSensorChangeValueEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + _updateDeviceFunctionFromCode(event.code, event.value); + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + try { + await controlDeviceService.controlDevice( + deviceUuid: deviceId, + status: Status(code: event.code, value: event.value), + ); + } catch (_) { + await _reloadDeviceStatus(); + } + } + + Future _onFlushMountedPresenceSensorBatchControlEvent( + FlushMountedPresenceSensorBatchControlEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + _updateDeviceFunctionFromCode(event.code, event.value); + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + + try { + await batchControlDevicesService.batchControlDevices( + uuids: event.deviceIds, + code: event.code, + value: event.value, + ); + } catch (_) { + await _reloadDeviceStatus(); + } + } + + void _updateDeviceFunctionFromCode(String code, int value) { + switch (code) { + case FlushMountedPresenceSensorModel.codeFarDetection: + deviceStatus.farDetection = value; + break; + case FlushMountedPresenceSensorModel.codeSensitivity: + deviceStatus.sensitivity = value; + break; + case FlushMountedPresenceSensorModel.codeNoneDelay: + deviceStatus.noneDelay = value; + break; + case FlushMountedPresenceSensorModel.codePresenceDelay: + deviceStatus.presenceDelay = value; + break; + case FlushMountedPresenceSensorModel.codeNearDetection: + deviceStatus.nearDetection = value; + break; + case FlushMountedPresenceSensorModel.codeOccurDistReduce: + deviceStatus.occurDistReduce = value; + break; + case FlushMountedPresenceSensorModel.codeSensiReduce: + deviceStatus.sensiReduce = value; + break; + default: + return; + } + } + + Future _reloadDeviceStatus() async { + await Future.delayed(const Duration(milliseconds: 500), () { + add(FlushMountedPresenceSensorFetchStatusEvent()); + }); + } + + Future _onFlushMountedPresenceSensorGetDeviceReportsEvent( + FlushMountedPresenceSensorGetDeviceReportsEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorDeviceReportsLoadingState()); + + try { + await DevicesManagementApi.getDeviceReports(deviceId, event.code) + .then((value) { + emit(FlushMountedPresenceSensorDeviceReportsState( + deviceReport: value, code: event.code)); + }); + } catch (e) { + emit(FlushMountedPresenceSensorDeviceReportsFailedState(error: e.toString())); + return; + } + } + + void _onFlushMountedPresenceSensorShowDescriptionEvent( + FlushMountedPresenceSensorShowDescriptionEvent event, + Emitter emit, + ) { + emit(FlushMountedPresenceSensorShowDescriptionState( + description: event.description)); + } + + void _onFlushMountedPresenceSensorBackToGridViewEvent( + FlushMountedPresenceSensorBackToGridViewEvent event, + Emitter emit, + ) { + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + } + + Future _onFlushMountedPresenceSensorFactoryResetEvent( + FlushMountedPresenceSensorFactoryResetEvent event, + Emitter emit, + ) async { + emit(FlushMountedPresenceSensorLoadingNewSate(model: deviceStatus)); + try { + final response = await DevicesManagementApi().factoryReset( + event.factoryReset, + event.deviceId, + ); + if (!response) { + emit( + const FlushMountedPresenceSensorFailedState( + error: 'Something went wrong with factory reset, please try again', + ), + ); + } else { + emit(FlushMountedPresenceSensorUpdateState(model: deviceStatus)); + } + } catch (e) { + emit(FlushMountedPresenceSensorFailedState(error: e.toString())); + } + } +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart new file mode 100644 index 00000000..f1636300 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_event.dart @@ -0,0 +1,84 @@ +part of 'flush_mounted_presence_sensor_bloc.dart'; + +sealed class FlushMountedPresenceSensorEvent extends Equatable { + const FlushMountedPresenceSensorEvent(); + + @override + List get props => []; +} + +class FlushMountedPresenceSensorFetchStatusEvent + extends FlushMountedPresenceSensorEvent {} + +class FlushMountedPresenceSensorChangeValueEvent + extends FlushMountedPresenceSensorEvent { + final int value; + final String code; + final bool isBatchControl; + const FlushMountedPresenceSensorChangeValueEvent({ + required this.value, + required this.code, + this.isBatchControl = false, + }); + + @override + List get props => [value, code]; +} + +class FlushMountedPresenceSensorFetchBatchStatusEvent + extends FlushMountedPresenceSensorEvent { + final List devicesIds; + const FlushMountedPresenceSensorFetchBatchStatusEvent(this.devicesIds); + + @override + List get props => [devicesIds]; +} + +class FlushMountedPresenceSensorGetDeviceReportsEvent + extends FlushMountedPresenceSensorEvent { + final String deviceUuid; + final String code; + const FlushMountedPresenceSensorGetDeviceReportsEvent({ + required this.deviceUuid, + required this.code, + }); + + @override + List get props => [deviceUuid, code]; +} + +class FlushMountedPresenceSensorShowDescriptionEvent + extends FlushMountedPresenceSensorEvent { + final String description; + const FlushMountedPresenceSensorShowDescriptionEvent({required this.description}); +} + +class FlushMountedPresenceSensorBackToGridViewEvent + extends FlushMountedPresenceSensorEvent {} + +class FlushMountedPresenceSensorBatchControlEvent + extends FlushMountedPresenceSensorEvent { + final List deviceIds; + final String code; + final dynamic value; + + const FlushMountedPresenceSensorBatchControlEvent({ + required this.deviceIds, + required this.code, + required this.value, + }); + + @override + List get props => [deviceIds, code, value]; +} + +class FlushMountedPresenceSensorFactoryResetEvent + extends FlushMountedPresenceSensorEvent { + final String deviceId; + final FactoryResetModel factoryReset; + + const FlushMountedPresenceSensorFactoryResetEvent({ + required this.deviceId, + required this.factoryReset, + }); +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_state.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_state.dart new file mode 100644 index 00000000..0fef07f2 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_state.dart @@ -0,0 +1,76 @@ +part of 'flush_mounted_presence_sensor_bloc.dart'; + +sealed class FlushMountedPresenceSensorState extends Equatable { + const FlushMountedPresenceSensorState(); + + @override + List get props => []; +} + +class FlushMountedPresenceSensorInitialState + extends FlushMountedPresenceSensorState {} + +class FlushMountedPresenceSensorLoadingInitialState + extends FlushMountedPresenceSensorState {} + +class FlushMountedPresenceSensorUpdateState extends FlushMountedPresenceSensorState { + final FlushMountedPresenceSensorModel model; + const FlushMountedPresenceSensorUpdateState({required this.model}); + + @override + List get props => [model]; +} + +class FlushMountedPresenceSensorLoadingNewSate + extends FlushMountedPresenceSensorState { + final FlushMountedPresenceSensorModel model; + const FlushMountedPresenceSensorLoadingNewSate({required this.model}); + + @override + List get props => [model]; +} + +class FlushMountedPresenceSensorFailedState extends FlushMountedPresenceSensorState { + final String error; + + const FlushMountedPresenceSensorFailedState({required this.error}); + + @override + List get props => [error]; +} + +class FlushMountedPresenceSensorDeviceReportsLoadingState + extends FlushMountedPresenceSensorState {} + +class FlushMountedPresenceSensorDeviceReportsState + extends FlushMountedPresenceSensorState { + const FlushMountedPresenceSensorDeviceReportsState({ + required this.deviceReport, + required this.code, + }); + + final DeviceReport deviceReport; + final String code; + + @override + List get props => [deviceReport, code]; +} + +class FlushMountedPresenceSensorDeviceReportsFailedState + extends FlushMountedPresenceSensorState { + const FlushMountedPresenceSensorDeviceReportsFailedState({required this.error}); + + final String error; + + @override + List get props => [error]; +} + +class FlushMountedPresenceSensorShowDescriptionState + extends FlushMountedPresenceSensorState { + const FlushMountedPresenceSensorShowDescriptionState({required this.description}); + + final String description; + @override + List get props => [description]; +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart new file mode 100644 index 00000000..f1342eec --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart @@ -0,0 +1,21 @@ +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart'; +import 'package:syncrow_web/services/batch_control_devices_service.dart'; +import 'package:syncrow_web/services/control_device_service.dart'; + +abstract final class FlushMountedPresenceSensorBlocFactory { + const FlushMountedPresenceSensorBlocFactory._(); + + static FlushMountedPresenceSensorBloc create({ + required String deviceId, + }) { + return FlushMountedPresenceSensorBloc( + deviceId: deviceId, + controlDeviceService: DebouncedControlDeviceService( + decoratee: RemoteControlDeviceService(), + ), + batchControlDevicesService: DebouncedBatchControlDevicesService( + decoratee: RemoteBatchControlDevicesService(), + ), + )..add(FlushMountedPresenceSensorFetchStatusEvent()); + } +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart new file mode 100644 index 00000000..975af0e8 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart @@ -0,0 +1,99 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class FlushMountedPresenceSensorModel { + FlushMountedPresenceSensorModel({ + required this.presenceState, + required this.farDetection, + required this.illuminance, + required this.sensitivity, + required this.occurDistReduce, + required this.noneDelay, + required this.presenceDelay, + required this.nearDetection, + required this.sensiReduce, + required this.checkingResult, + }); + + static const String codePresenceState = 'presence_state'; + static const String codeSensitivity = 'sensitivity'; + static const String codeNearDetection = 'near_detection'; + static const String codeFarDetection = 'far_detection'; + static const String codeCheckingResult = 'checking_result'; + static const String codePresenceDelay = 'presence_delay'; + static const String codeNoneDelay = 'none_delay'; + static const String codeOccurDistReduce = 'occur_dist_reduce'; + static const String codeIlluminance = 'illum_value'; + static const String codeSensiReduce = 'sensi_reduce'; + + String presenceState; + int sensitivity; + int nearDetection; + int farDetection; + String checkingResult; + int presenceDelay; + int noneDelay; + int occurDistReduce; + int illuminance; + int sensiReduce; + + factory FlushMountedPresenceSensorModel.fromJson(List jsonList) { + String presenceState = 'none'; + int sensitivity = 0; + int nearDetection = 0; + int farDetection = 0; + String checkingResult = 'none'; + int presenceDelay = 0; + int noneDelay = 0; + int occurDistReduce = 0; + int illuminance = 0; + int sensiReduce = 0; + + for (var status in jsonList) { + switch (status.code) { + case codePresenceState: + presenceState = status.value ?? 'presence'; + break; + case codeSensitivity: + sensitivity = status.value ?? 0; + break; + case codeNearDetection: + nearDetection = status.value ?? 0; + break; + case codeFarDetection: + farDetection = status.value ?? 0; + break; + case codeCheckingResult: + checkingResult = status.value ?? 'check_success'; + break; + case codePresenceDelay: + presenceDelay = status.value ?? 0; + break; + case codeNoneDelay: + noneDelay = status.value ?? 0; + break; + case codeOccurDistReduce: + occurDistReduce = status.value ?? 0; + break; + case codeIlluminance: + illuminance = status.value ?? 0; + break; + case codeSensiReduce: + sensiReduce = status.value ?? 0; + break; + } + } + + return FlushMountedPresenceSensorModel( + presenceState: presenceState, + sensitivity: sensitivity, + nearDetection: nearDetection, + farDetection: farDetection, + checkingResult: checkingResult, + presenceDelay: presenceDelay, + noneDelay: noneDelay, + occurDistReduce: occurDistReduce, + illuminance: illuminance, + sensiReduce: sensiReduce, + ); + } +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart new file mode 100644 index 00000000..7f1e726c --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_batch_control_view.dart @@ -0,0 +1,176 @@ +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/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/batch_control/factory_reset.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class FlushMountedPresenceSensorBatchControlView extends StatelessWidget + with HelperResponsiveLayout { + const FlushMountedPresenceSensorBatchControlView({ + required this.devicesIds, + super.key, + }); + + final List devicesIds; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => FlushMountedPresenceSensorBlocFactory.create( + deviceId: devicesIds.first, + ), + child: BlocBuilder( + builder: (context, state) { + if (state is FlushMountedPresenceSensorLoadingInitialState || + state is FlushMountedPresenceSensorDeviceReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is FlushMountedPresenceSensorUpdateState) { + return _buildGridView(context, state.model); + } + return const Center(child: Text('Error fetching status')); + }, + ), + ); + } + + Widget _buildGridView( + BuildContext context, + FlushMountedPresenceSensorModel model, + ) { + 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: [ + PresenceUpdateData( + value: model.sensitivity.toDouble(), + title: 'Sensitivity:', + minValue: 0, + maxValue: 9, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeSensitivity, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.nearDetection / 100).toDouble(), + title: 'Nearest Detect Dist:', + description: 'm', + minValue: 0.0, + maxValue: 9.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNearDetection, + value: (value * 100).toInt(), + ), + ), + ), + PresenceUpdateData( + value: (model.farDetection / 100).toDouble(), + title: 'Max Detect Dist:', + description: 'm', + minValue: 0.0, + maxValue: 9.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeFarDetection, + value: (value * 100).toInt(), + ), + ), + ), + PresenceUpdateData( + value: model.presenceDelay.toDouble(), + title: 'Trigger Level:', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codePresenceDelay, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.occurDistReduce.toDouble()), + title: 'Indent Level:', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeOccurDistReduce, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.sensiReduce.toDouble()), + title: 'Target Confirm Time:', + description: 's', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeSensiReduce, + value: value, + ), + ), + ), + PresenceUpdateData( + value: ((model.noneDelay / 10).toDouble()), + description: 's', + title: 'Disappe Delay:', + minValue: 20, + maxValue: 300, + steps: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNoneDelay, + value: (value * 10).round(), + ), + ), + ), + FactoryResetWidget( + callFactoryReset: () { + context.read().add( + FlushMountedPresenceSensorFactoryResetEvent( + deviceId: devicesIds.first, + factoryReset: FactoryResetModel(devicesUuid: devicesIds), + ), + ); + }, + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart new file mode 100644 index 00000000..bdefa507 --- /dev/null +++ b/lib/pages/device_managment/flush_mounted_presence_sensor/views/flush_mounted_presence_sensor_control_view.dart @@ -0,0 +1,217 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/devices_model.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/bloc/flush_mounted_presence_sensor_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/factories/flush_mounted_presence_sensor_bloc_factory.dart'; +import 'package:syncrow_web/pages/device_managment/flush_mounted_presence_sensor/models/flush_mounted_presence_sensor_model.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_display_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_static_widget.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_status.dart'; +import 'package:syncrow_web/pages/device_managment/shared/sensors_widgets/presence_update_data.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/description_view.dart'; +import 'package:syncrow_web/pages/device_managment/shared/table/report_table.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/helpers/responsice_layout_helper/responsive_layout_helper.dart'; + +class FlushMountedPresenceSensorControlView extends StatelessWidget + with HelperResponsiveLayout { + const FlushMountedPresenceSensorControlView({super.key, required this.device}); + + final AllDevicesModel device; + + @override + Widget build(BuildContext context) { + return BlocProvider( + create: (context) => FlushMountedPresenceSensorBlocFactory.create( + deviceId: device.uuid ?? '-1', + ), + child: BlocBuilder( + builder: (context, state) { + if (state is FlushMountedPresenceSensorLoadingInitialState || + state is FlushMountedPresenceSensorDeviceReportsLoadingState) { + return const Center(child: CircularProgressIndicator()); + } else if (state is FlushMountedPresenceSensorUpdateState) { + return _buildGridView(context, state.model); + } else if (state is FlushMountedPresenceSensorDeviceReportsState) { + return ReportsTable( + report: state.deviceReport, + thirdColumnTitle: + state.code == 'illuminance_value' ? "Value" : 'Status', + thirdColumnDescription: + state.code == 'illuminance_value' ? "Lux" : null, + onRowTap: (index) {}, + onClose: () { + context + .read() + .add(FlushMountedPresenceSensorBackToGridViewEvent()); + }, + ); + } else if (state is FlushMountedPresenceSensorShowDescriptionState) { + return DescriptionView( + description: state.description, + onClose: () { + context + .read() + .add(FlushMountedPresenceSensorBackToGridViewEvent()); + }, + ); + } else if (state is FlushMountedPresenceSensorDeviceReportsFailedState) { + final model = + context.read().deviceStatus; + return _buildGridView(context, model); + } + return const Center( + child: Text('Error fetching status', textAlign: TextAlign.center), + ); + }, + ), + ); + } + + Widget _buildGridView( + BuildContext context, + FlushMountedPresenceSensorModel model, + ) { + 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: [ + PresenceState( + value: model.presenceState, + ), + PresenceDisplayValue( + value: model.illuminance.toString(), + postfix: 'Lux', + description: 'Illuminance Value', + ), + PresenceUpdateData( + value: model.sensitivity.toDouble(), + title: 'Sensitivity:', + minValue: 0, + maxValue: 9, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeSensitivity, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.nearDetection / 100).toDouble(), + title: 'Nearest Detect Dist:', + description: 'm', + minValue: 0.0, + maxValue: 9.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNearDetection, + value: (value * 100).toInt(), + ), + ), + ), + PresenceUpdateData( + value: (model.farDetection / 100).toDouble(), + title: 'Max Detect Dist:', + description: 'm', + minValue: 0.0, + maxValue: 9.5, + steps: 0.1, + valuesPercision: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeFarDetection, + value: (value * 100).toInt(), + ), + ), + ), + PresenceUpdateData( + value: (model.presenceDelay.toDouble()), + title: 'Trigger Level:', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codePresenceDelay, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.occurDistReduce.toDouble()), + title: 'Indent Level:', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeOccurDistReduce, + value: value, + ), + ), + ), + PresenceUpdateData( + value: (model.sensiReduce.toDouble()), + title: 'Target Confirm Time:', + description: 's', + minValue: 0, + maxValue: 3, + steps: 1, + action: (int value) => context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeSensiReduce, + value: value, + ), + ), + ), + PresenceUpdateData( + value: ((model.noneDelay / 10).toDouble()), + description: 's', + title: 'Disappe Delay:', + minValue: 20, + maxValue: 300, + steps: 1, + action: (double value) => + context.read().add( + FlushMountedPresenceSensorChangeValueEvent( + code: FlushMountedPresenceSensorModel.codeNoneDelay, + value: (value * 10).round(), + ), + ), + ), + GestureDetector( + onTap: () => context.read().add( + FlushMountedPresenceSensorGetDeviceReportsEvent( + code: 'presence_state', + deviceUuid: device.uuid!, + ), + ), + child: const PresenceStaticWidget( + icon: Assets.presenceRecordIcon, + description: 'Presence Record', + ), + ), + ], + ); + } +} diff --git a/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart b/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart index 16596a1f..e535612d 100644 --- a/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart +++ b/lib/pages/device_managment/shared/sensors_widgets/presence_update_data.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/device_managment/shared/device_controls_container.dart'; import 'package:syncrow_web/pages/device_managment/shared/increament_decreament.dart'; import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; class PresenceUpdateData extends StatefulWidget { const PresenceUpdateData({ @@ -13,6 +14,7 @@ class PresenceUpdateData extends StatefulWidget { required this.maxValue, required this.steps, this.description, + this.valuesPercision = 0, }); final String title; @@ -22,6 +24,7 @@ class PresenceUpdateData extends StatefulWidget { final double steps; final Function action; final String? description; + final int valuesPercision; @override State createState() => _CurrentTempState(); @@ -45,7 +48,7 @@ class _CurrentTempState extends State { } void _onValueChanged(double newValue) { - widget.action(newValue.toInt()); + widget.action(newValue); } @override @@ -62,11 +65,14 @@ class _CurrentTempState extends State { children: [ Text( widget.title, - style: Theme.of(context).textTheme.bodySmall!.copyWith( - color: ColorsManager.blackColor, fontWeight: FontWeight.w400, fontSize: 10), + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 10, + ), ), IncrementDecrementWidget( - value: widget.value.toString(), + value: widget.value.toStringAsFixed(widget.valuesPercision), description: widget.description ?? '', descriptionColor: ColorsManager.blackColor, onIncrement: () { diff --git a/lib/services/batch_control_devices_service.dart b/lib/services/batch_control_devices_service.dart new file mode 100644 index 00000000..76dbe480 --- /dev/null +++ b/lib/services/batch_control_devices_service.dart @@ -0,0 +1,85 @@ +import 'dart:developer'; + +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +abstract interface class BatchControlDevicesService { + Future batchControlDevices({ + required List uuids, + required String code, + required Object value, + }); +} + +final class RemoteBatchControlDevicesService implements BatchControlDevicesService { + @override + Future batchControlDevices({ + required List uuids, + required String code, + required Object value, + }) async { + try { + final body = { + 'devicesUuid': uuids, + 'code': code, + 'value': value, + 'operationType': 'COMMAND', + }; + + final response = await HTTPService().post( + path: ApiEndpoints.deviceBatchControl, + body: body, + showServerMessage: true, + expectedResponseModel: (json) => (json['success'] as bool?) ?? false, + ); + + return response; + } catch (e) { + log('Error fetching $e', name: 'BatchControlDevicesService'); + return false; + } + } +} + +final class DebouncedBatchControlDevicesService + implements BatchControlDevicesService { + final BatchControlDevicesService decoratee; + final Duration debounceDuration; + + final _pendingRequests = <(List uuids, String code, Object value)>[]; + var _isProcessing = false; + + DebouncedBatchControlDevicesService({ + required this.decoratee, + this.debounceDuration = const Duration(milliseconds: 1500), + }); + + @override + Future batchControlDevices({ + required List uuids, + required String code, + required Object value, + }) async { + _pendingRequests.add((uuids, code, value)); + + if (_isProcessing) return false; + + _isProcessing = true; + + await Future.delayed(debounceDuration); + + final lastRequest = _pendingRequests.last; + _pendingRequests.clear(); + + try { + final (lastRequestUuids, lastRequestCode, lastRequestValue) = lastRequest; + return decoratee.batchControlDevices( + uuids: lastRequestUuids, + code: lastRequestCode, + value: lastRequestValue, + ); + } finally { + _isProcessing = false; + } + } +} diff --git a/lib/services/control_device_service.dart b/lib/services/control_device_service.dart new file mode 100644 index 00000000..ab04a398 --- /dev/null +++ b/lib/services/control_device_service.dart @@ -0,0 +1,75 @@ +import 'dart:developer'; + +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/services/api/http_service.dart'; +import 'package:syncrow_web/utils/constants/api_const.dart'; + +abstract interface class ControlDeviceService { + Future controlDevice({ + required String deviceUuid, + required Status status, + }); +} + +final class RemoteControlDeviceService implements ControlDeviceService { + @override + Future controlDevice({ + required String deviceUuid, + required Status status, + }) async { + try { + final response = await HTTPService().post( + path: ApiEndpoints.deviceControl.replaceAll('{uuid}', deviceUuid), + body: status.toMap(), + showServerMessage: true, + expectedResponseModel: (json) { + return (json['success'] as bool?) ?? false; + }, + ); + return response; + } catch (e) { + log('Error fetching $e', name: 'ControlDeviceService'); + return false; + } + } +} + +final class DebouncedControlDeviceService implements ControlDeviceService { + final ControlDeviceService decoratee; + final Duration debounceDuration; + + DebouncedControlDeviceService({ + required this.decoratee, + this.debounceDuration = const Duration(milliseconds: 1500), + }); + + final _pendingRequests = <(String deviceUuid, Status status)>[]; + var _isProcessing = false; + + @override + Future controlDevice({ + required String deviceUuid, + required Status status, + }) async { + _pendingRequests.add((deviceUuid, status)); + + if (_isProcessing) return false; + + _isProcessing = true; + + await Future.delayed(debounceDuration); + + final lastRequest = _pendingRequests.last; + _pendingRequests.clear(); + + try { + final (lastRequestDeviceUuid, lastRequestStatus) = lastRequest; + return decoratee.controlDevice( + deviceUuid: lastRequestDeviceUuid, + status: lastRequestStatus, + ); + } finally { + _isProcessing = false; + } + } +} diff --git a/pubspec.yaml b/pubspec.yaml index fd7ed797..ec8660b6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -60,6 +60,7 @@ dependencies: firebase_core: ^3.11.0 firebase_crashlytics: ^4.3.2 firebase_database: ^11.3.2 + bloc: ^8.1.4 dev_dependencies: