making good progress on realtime data integration.

This commit is contained in:
Faris Armoush
2025-05-05 16:34:46 +03:00
parent 1a3006fa43
commit 4d5de7bc05
8 changed files with 210 additions and 64 deletions

View File

@ -1,6 +1,7 @@
import 'package:bloc/bloc.dart'; import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.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/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'; import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
part 'power_clamp_info_event.dart'; part 'power_clamp_info_event.dart';
@ -11,6 +12,7 @@ class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState>
this._powerClampInfoService, this._powerClampInfoService,
) : super(const PowerClampInfoState()) { ) : super(const PowerClampInfoState()) {
on<LoadPowerClampInfoEvent>(_onLoadPowerClampInfoEvent); on<LoadPowerClampInfoEvent>(_onLoadPowerClampInfoEvent);
on<UpdatePowerClampStatusEvent>(_onUpdatePowerClampStatusEvent);
} }
final PowerClampInfoService _powerClampInfoService; final PowerClampInfoService _powerClampInfoService;
@ -37,4 +39,9 @@ class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState>
); );
} }
} }
void _onUpdatePowerClampStatusEvent(
UpdatePowerClampStatusEvent event,
Emitter<PowerClampInfoState> emit,
) async {}
} }

View File

@ -15,3 +15,13 @@ final class LoadPowerClampInfoEvent extends PowerClampInfoEvent {
@override @override
List<Object> get props => [deviceId]; List<Object> get props => [deviceId];
} }
final class UpdatePowerClampStatusEvent extends PowerClampInfoEvent {
const UpdatePowerClampStatusEvent(this.statusList);
final List<Status> statusList;
@override
List<Object> get props => [statusList];
}

View File

@ -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<RealtimeDeviceChangesEvent, RealtimeDeviceChangesState> {
RealtimeDeviceChangesBloc(
this._realtimeDeviceService,
) : super(const RealtimeDeviceChangesState()) {
on<RealtimeDeviceChangesStarted>(_onRealtimeDeviceChangesStarted);
}
final RealtimeDeviceService _realtimeDeviceService;
Future<void> _onRealtimeDeviceChangesStarted(
RealtimeDeviceChangesStarted event,
Emitter<RealtimeDeviceChangesState> 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',
),
),
);
}
}

View File

@ -0,0 +1,17 @@
part of 'realtime_device_changes_bloc.dart';
sealed class RealtimeDeviceChangesEvent extends Equatable {
const RealtimeDeviceChangesEvent();
@override
List<Object> get props => [];
}
final class RealtimeDeviceChangesStarted extends RealtimeDeviceChangesEvent {
const RealtimeDeviceChangesStarted(this.deviceId);
final String deviceId;
@override
List<Object> get props => [deviceId];
}

View File

@ -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 <Status>[],
this.errorMessage,
});
final RealtimeDeviceChangesStatus status;
final List<Status> deviceStatusList;
final String? errorMessage;
RealtimeDeviceChangesState copyWith({
RealtimeDeviceChangesStatus? status,
List<Status>? deviceStatusList,
String? errorMessage,
}) {
return RealtimeDeviceChangesState(
status: status ?? this.status,
deviceStatusList: deviceStatusList ?? this.deviceStatusList,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => [status, deviceStatusList, errorMessage];
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.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/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/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/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_data_device_dropdown.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_status_widget.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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return BlocBuilder<PowerClampInfoBloc, PowerClampInfoState>( return BlocListener<RealtimeDeviceChangesBloc, RealtimeDeviceChangesState>(
builder: (context, state) { listener: (context, state) {
final generalDataPoints = context.read<PowerClampInfoBloc>().add(
state.powerClampModel?.status.general.dataPoints ?? []; UpdatePowerClampStatusEvent(state.deviceStatusList),
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()),
],
);
}, },
child: BlocBuilder<PowerClampInfoBloc, PowerClampInfoState>(
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()),
],
);
},
),
); );
} }

View File

@ -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<Status> 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<dynamic, dynamic>?;
if (data == null || data['status'] == null) {
throw Exception('Invalid data received from Firebase');
}
final statusMap = data['status'] as Map<dynamic, dynamic>;
return Status(
code: statusMap['code'] as String? ?? '',
value: statusMap['value'] as String? ?? '',
);
});
} catch (e) {
throw Exception('Error subscribing to device status: $e');
}
}
}

View File

@ -0,0 +1,5 @@
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
abstract interface class RealtimeDeviceService {
Stream<Status> subscribe(String deviceId);
}