Power clamp info integration.

This commit is contained in:
Faris Armoush
2025-05-05 12:56:59 +03:00
parent 490ca2057e
commit 1a3006fa43
10 changed files with 259 additions and 62 deletions

View File

@ -5,11 +5,14 @@ import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_
import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_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/total_energy_consumption/total_energy_consumption_bloc.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/fake_energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/fake_energy_consumption_per_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/remote_power_clamp_info_service.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/fake_total_energy_consumption_service.dart';
import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart';
import 'package:syncrow_web/services/api/http_service.dart';
import 'package:syncrow_web/utils/theme/responsive_text_theme.dart';
import 'package:syncrow_web/web_layout/web_scaffold.dart';
@ -38,6 +41,11 @@ class AnalyticsPage extends StatelessWidget {
FakeEnergyConsumptionPerDeviceService(),
),
),
BlocProvider(
create: (context) => PowerClampInfoBloc(
RemotePowerClampInfoService(HTTPService()),
),
),
],
child: const AnalyticsPageForm(),
);

View File

@ -0,0 +1,40 @@
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/power_clamp/models/power_clamp_model.dart';
part 'power_clamp_info_event.dart';
part 'power_clamp_info_state.dart';
class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState> {
PowerClampInfoBloc(
this._powerClampInfoService,
) : super(const PowerClampInfoState()) {
on<LoadPowerClampInfoEvent>(_onLoadPowerClampInfoEvent);
}
final PowerClampInfoService _powerClampInfoService;
Future<void> _onLoadPowerClampInfoEvent(
LoadPowerClampInfoEvent event,
Emitter<PowerClampInfoState> emit,
) async {
emit(state.copyWith(status: PowerClampInfoStatus.loading));
try {
final powerClampModel = await _powerClampInfoService.getInfo(event.deviceId);
emit(
state.copyWith(
status: PowerClampInfoStatus.loaded,
powerClampModel: powerClampModel,
),
);
} catch (e) {
emit(
state.copyWith(
status: PowerClampInfoStatus.error,
errorMessage: e.toString(),
),
);
}
}
}

View File

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

View File

@ -0,0 +1,30 @@
part of 'power_clamp_info_bloc.dart';
enum PowerClampInfoStatus { initial, loading, loaded, error }
final class PowerClampInfoState extends Equatable {
const PowerClampInfoState({
this.status = PowerClampInfoStatus.initial,
this.powerClampModel,
this.errorMessage,
});
final PowerClampInfoStatus status;
final PowerClampModel? powerClampModel;
final String? errorMessage;
PowerClampInfoState copyWith({
PowerClampInfoStatus? status,
PowerClampModel? powerClampModel,
String? errorMessage,
}) {
return PowerClampInfoState(
status: status ?? this.status,
powerClampModel: powerClampModel ?? this.powerClampModel,
errorMessage: errorMessage ?? this.errorMessage,
);
}
@override
List<Object?> get props => [status, powerClampModel, errorMessage];
}

View File

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_by_phases/energy_consumption_by_phases_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/energy_consumption_per_device/energy_consumption_per_device_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/total_energy_consumption/total_energy_consumption_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/energy_consumption_per_device_chart_box.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart';
@ -26,6 +27,7 @@ class _AnalyticsEnergyManagementViewState
_loadEnergyConsumptionByPhases();
_loadTotalEnergyConsumption();
_loadEnergyConsumptionPerDevice();
_loadPowerClampInfo();
super.initState();
}
@ -50,6 +52,11 @@ class _AnalyticsEnergyManagementViewState
);
}
void _loadPowerClampInfo() {
context.read<PowerClampInfoBloc>().add(
const LoadPowerClampInfoEvent('deviceId'),
);
}
static const _padding = EdgeInsetsDirectional.all(32);

View File

@ -15,6 +15,7 @@ class EnergyConsumptionByPhasesTitle extends StatelessWidget {
Expanded(
flex: 4,
child: FittedBox(
alignment: AlignmentDirectional.centerStart,
fit: BoxFit.scaleDown,
child: ChartTitle(
title: Text(

View File

@ -1,71 +1,86 @@
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/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';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.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:uuid/uuid.dart';
class PowerClampEnergyDataWidget extends StatelessWidget {
const PowerClampEnergyDataWidget({super.key});
@override
Widget build(BuildContext context) {
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(
const Uuid().v6(),
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
const Divider(),
const Expanded(
flex: 2,
child: PowerClampEnergyStatusWidget(
status: [
PowerClampEnergyStatus(
iconPath: Assets.powerActiveIcon,
title: 'Active',
value: '700',
unit: 'W',
return 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,
),
PowerClampEnergyStatus(
iconPath: Assets.voltMeterIcon,
title: 'Current',
value: '3.06',
unit: 'A',
),
const SizedBox(height: 6),
SelectableText(
state.powerClampModel?.productUuid ?? 'N/A',
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.blackColor,
fontWeight: FontWeight.w400,
fontSize: 12,
),
PowerClampEnergyStatus(
iconPath: Assets.frequencyIcon,
title: 'Frequency',
value: '50',
unit: 'Hz',
),
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),
const Expanded(flex: 4, child: PowerClampPhasesDataWidget()),
const SizedBox(height: 14),
const Expanded(flex: 3, child: EnergyConsumptionByPhasesChartBox()),
],
),
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()),
],
);
},
);
}
@ -99,4 +114,11 @@ class PowerClampEnergyDataWidget extends StatelessWidget {
],
);
}
String _valueFromCode(String code, List<DataPoint> points) {
return points
.firstWhere((e) => e.code == code, orElse: () => DataPoint(value: '--'))
.value
.toString();
}
}

View File

@ -1,21 +1,37 @@
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phase.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.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/style.dart';
class PowerClampPhasesDataWidget extends StatelessWidget {
const PowerClampPhasesDataWidget({super.key});
const PowerClampPhasesDataWidget({
required this.phaseA,
required this.phaseB,
required this.phaseC,
super.key,
});
final Phase? phaseA;
final Phase? phaseB;
final Phase? phaseC;
@override
Widget build(BuildContext context) {
final phases = [phaseA, phaseB, phaseC];
return Container(
width: double.infinity,
decoration: secondarySection.copyWith(boxShadow: const []),
child: Row(
children: List.generate(5, (index) {
if (index case 1 || 3) return _buildSeparator();
if (index.isOdd) return _buildSeparator();
final phaseIndex = index ~/ 2;
final phase = phases[phaseIndex];
final phaseSuffix = ['A', 'B', 'C'][phaseIndex];
return Expanded(
child: Padding(
padding: const EdgeInsetsDirectional.symmetric(horizontal: 14),
@ -30,7 +46,7 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
alignment: AlignmentDirectional.center,
fit: BoxFit.scaleDown,
child: Text(
'Phase ${index ~/ 2 + 1}',
'Phase ${phaseIndex + 1}',
style: context.textTheme.titleLarge?.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w700,
@ -39,28 +55,40 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
),
),
),
const PowerClampPhase(
PowerClampPhase(
iconPath: Assets.powerActiveIcon,
title: 'Active Power',
value: '393.0',
value: _valueFromCode(
code: 'ReactivePower$phaseSuffix',
points: phase?.dataPoints,
),
unit: 'W',
),
const PowerClampPhase(
PowerClampPhase(
iconPath: Assets.voltageIcon,
title: 'Active Power',
value: '228.5',
title: 'Voltage',
value: _valueFromCode(
code: 'Voltage$phaseSuffix',
points: phase?.dataPoints,
),
unit: 'V',
),
const PowerClampPhase(
PowerClampPhase(
iconPath: Assets.voltMeterIcon,
title: 'Current',
value: '1.72',
value: _valueFromCode(
code: 'Current$phaseSuffix',
points: phase?.dataPoints,
),
unit: 'A',
),
const PowerClampPhase(
PowerClampPhase(
iconPath: Assets.speedoMeter,
title: 'Power Factor',
value: '0.8',
value: _valueFromCode(
code: 'PowerFactor$phaseSuffix',
points: phase?.dataPoints,
),
),
const SizedBox(height: 8),
],
@ -92,4 +120,16 @@ class PowerClampPhasesDataWidget extends StatelessWidget {
),
);
}
String _valueFromCode({
required String code,
required List<DataPoint>? points,
}) {
final element = points?.firstWhere(
(e) => e.code == code,
orElse: () => DataPoint(value: '--'),
);
return element?.value.toString() ?? '--';
}
}

View File

@ -0,0 +1,5 @@
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
abstract interface class PowerClampInfoService {
Future<PowerClampModel> getInfo(String deviceId);
}

View File

@ -0,0 +1,27 @@
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemotePowerClampInfoService implements PowerClampInfoService {
const RemotePowerClampInfoService(this._httpService);
final HTTPService _httpService;
@override
Future<PowerClampModel> getInfo(String deviceId) async {
try {
final response = await _httpService.get(
path: '/devices/cb71d6ad-6e29-4eaa-ae3e-1a0d1c5f60fa/functions/status',
showServerMessage: true,
expectedResponseModel: (data) {
final json = data as Map<String, Object?>? ?? {};
final mappedData = json['data'] as Map<String, Object?>? ?? {};
return PowerClampModel.fromJson(mappedData);
},
);
return response;
} catch (e) {
throw Exception('Failed to fetch power clamp info: $e');
}
}
}