diff --git a/lib/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart b/lib/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart index f5264867..1e9ddfb7 100644 --- a/lib/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart +++ b/lib/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart'; part 'power_clamp_info_event.dart'; @@ -11,6 +12,7 @@ class PowerClampInfoBloc extends Bloc this._powerClampInfoService, ) : super(const PowerClampInfoState()) { on(_onLoadPowerClampInfoEvent); + on(_onUpdatePowerClampStatusEvent); } final PowerClampInfoService _powerClampInfoService; @@ -37,4 +39,9 @@ class PowerClampInfoBloc extends Bloc ); } } + + void _onUpdatePowerClampStatusEvent( + UpdatePowerClampStatusEvent event, + Emitter emit, + ) async {} } diff --git a/lib/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_event.dart b/lib/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_event.dart index 2b13f6b9..acf5e967 100644 --- a/lib/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_event.dart +++ b/lib/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_event.dart @@ -15,3 +15,13 @@ final class LoadPowerClampInfoEvent extends PowerClampInfoEvent { @override List get props => [deviceId]; } + + +final class UpdatePowerClampStatusEvent extends PowerClampInfoEvent { + const UpdatePowerClampStatusEvent(this.statusList); + + final List statusList; + + @override + List get props => [statusList]; +} \ No newline at end of file diff --git a/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart b/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart new file mode 100644 index 00000000..306dd43c --- /dev/null +++ b/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart @@ -0,0 +1,41 @@ +import 'package:bloc/bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:syncrow_web/pages/analytics/services/realtime_device_service/realtime_device_service.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +part 'realtime_device_changes_event.dart'; +part 'realtime_device_changes_state.dart'; + +class RealtimeDeviceChangesBloc + extends Bloc { + RealtimeDeviceChangesBloc( + this._realtimeDeviceService, + ) : super(const RealtimeDeviceChangesState()) { + on(_onRealtimeDeviceChangesStarted); + } + + final RealtimeDeviceService _realtimeDeviceService; + + Future _onRealtimeDeviceChangesStarted( + RealtimeDeviceChangesStarted event, + Emitter emit, + ) async { + await emit.onEach( + _realtimeDeviceService.subscribe(event.deviceId), + onData: (data) { + final currentState = state; + emit( + state.copyWith( + deviceStatusList: [...currentState.deviceStatusList, data], + ), + ); + }, + onError: (error, _) => emit( + state.copyWith( + status: RealtimeDeviceChangesStatus.error, + errorMessage: '$error', + ), + ), + ); + } +} diff --git a/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_event.dart b/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_event.dart new file mode 100644 index 00000000..b1d5c793 --- /dev/null +++ b/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_event.dart @@ -0,0 +1,17 @@ +part of 'realtime_device_changes_bloc.dart'; + +sealed class RealtimeDeviceChangesEvent extends Equatable { + const RealtimeDeviceChangesEvent(); + + @override + List get props => []; +} + +final class RealtimeDeviceChangesStarted extends RealtimeDeviceChangesEvent { + const RealtimeDeviceChangesStarted(this.deviceId); + + final String deviceId; + + @override + List get props => [deviceId]; +} diff --git a/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_state.dart b/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_state.dart new file mode 100644 index 00000000..3bc4c3ab --- /dev/null +++ b/lib/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_state.dart @@ -0,0 +1,30 @@ +part of 'realtime_device_changes_bloc.dart'; + +enum RealtimeDeviceChangesStatus { initial, loading, loaded, error } + +final class RealtimeDeviceChangesState extends Equatable { + const RealtimeDeviceChangesState({ + this.status = RealtimeDeviceChangesStatus.initial, + this.deviceStatusList = const [], + this.errorMessage, + }); + + final RealtimeDeviceChangesStatus status; + final List deviceStatusList; + final String? errorMessage; + + RealtimeDeviceChangesState copyWith({ + RealtimeDeviceChangesStatus? status, + List? deviceStatusList, + String? errorMessage, + }) { + return RealtimeDeviceChangesState( + status: status ?? this.status, + deviceStatusList: deviceStatusList ?? this.deviceStatusList, + errorMessage: errorMessage ?? this.errorMessage, + ); + } + + @override + List get props => [status, deviceStatusList, errorMessage]; +} diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart index 43ab5277..837248d6 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/models/power_clamp_energy_status.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/power_clamp_info/power_clamp_info_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_by_phases_chart_box.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_device_dropdown.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.dart'; @@ -16,71 +17,78 @@ class PowerClampEnergyDataWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return BlocBuilder( - builder: (context, state) { - final generalDataPoints = - state.powerClampModel?.status.general.dataPoints ?? []; - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _buildHeader(context), - Text( - 'Device ID:', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - ), - const SizedBox(height: 6), - SelectableText( - state.powerClampModel?.productUuid ?? 'N/A', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - ), - const Divider(), - Expanded( - flex: 2, - child: PowerClampEnergyStatusWidget( - status: [ - PowerClampEnergyStatus( - iconPath: Assets.powerActiveIcon, - title: 'Active', - value: _valueFromCode('EnergyConsumed', generalDataPoints), - unit: 'W', - ), - PowerClampEnergyStatus( - iconPath: Assets.voltMeterIcon, - title: 'Current', - value: _valueFromCode('Current', generalDataPoints), - unit: 'A', - ), - PowerClampEnergyStatus( - iconPath: Assets.frequencyIcon, - title: 'Frequency', - value: _valueFromCode('Frequency', generalDataPoints), - unit: 'Hz', - ), - ], - ), - ), - const SizedBox(height: 14), - Expanded( - flex: 4, - child: PowerClampPhasesDataWidget( - phaseA: state.powerClampModel?.status.phaseA, - phaseB: state.powerClampModel?.status.phaseB, - phaseC: state.powerClampModel?.status.phaseC, - ), - ), - const SizedBox(height: 14), - const Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()), - ], - ); + return BlocListener( + listener: (context, state) { + context.read().add( + UpdatePowerClampStatusEvent(state.deviceStatusList), + ); }, + child: BlocBuilder( + builder: (context, state) { + final generalDataPoints = + state.powerClampModel?.status.general.dataPoints ?? []; + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + Text( + 'Device ID:', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + const SizedBox(height: 6), + SelectableText( + state.powerClampModel?.productUuid ?? 'N/A', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + const Divider(), + Expanded( + flex: 2, + child: PowerClampEnergyStatusWidget( + status: [ + PowerClampEnergyStatus( + iconPath: Assets.powerActiveIcon, + title: 'Active', + value: _valueFromCode('EnergyConsumed', generalDataPoints), + unit: 'W', + ), + PowerClampEnergyStatus( + iconPath: Assets.voltMeterIcon, + title: 'Current', + value: _valueFromCode('Current', generalDataPoints), + unit: 'A', + ), + PowerClampEnergyStatus( + iconPath: Assets.frequencyIcon, + title: 'Frequency', + value: _valueFromCode('Frequency', generalDataPoints), + unit: 'Hz', + ), + ], + ), + ), + const SizedBox(height: 14), + Expanded( + flex: 4, + child: PowerClampPhasesDataWidget( + phaseA: state.powerClampModel?.status.phaseA, + phaseB: state.powerClampModel?.status.phaseB, + phaseC: state.powerClampModel?.status.phaseC, + ), + ), + const SizedBox(height: 14), + const Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()), + ], + ); + }, + ), ); } diff --git a/lib/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart b/lib/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart new file mode 100644 index 00000000..ae53b813 --- /dev/null +++ b/lib/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart @@ -0,0 +1,28 @@ +import 'package:firebase_database/firebase_database.dart'; +import 'package:syncrow_web/pages/analytics/services/realtime_device_service/realtime_device_service.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +class FirebaseRealtimeDeviceService implements RealtimeDeviceService { + @override + Stream subscribe(String deviceId) { + try { + final ref = FirebaseDatabase.instance.ref('device-status/$deviceId'); + + return ref.onValue.asyncMap((DatabaseEvent event) { + final data = event.snapshot.value as Map?; + + if (data == null || data['status'] == null) { + throw Exception('Invalid data received from Firebase'); + } + + final statusMap = data['status'] as Map; + return Status( + code: statusMap['code'] as String? ?? '', + value: statusMap['value'] as String? ?? '', + ); + }); + } catch (e) { + throw Exception('Error subscribing to device status: $e'); + } + } +} diff --git a/lib/pages/analytics/services/realtime_device_service/realtime_device_service.dart b/lib/pages/analytics/services/realtime_device_service/realtime_device_service.dart new file mode 100644 index 00000000..32eb7781 --- /dev/null +++ b/lib/pages/analytics/services/realtime_device_service/realtime_device_service.dart @@ -0,0 +1,5 @@ +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; + +abstract interface class RealtimeDeviceService { + Stream subscribe(String deviceId); +} \ No newline at end of file