mirror of
https://github.com/SyncrowIOT/web.git
synced 2025-07-09 22:57:21 +00:00
SP-1723-FE-Integrate-Charts-with-API-s-for-AQI-sensor.
This commit is contained in:
@ -15,7 +15,9 @@ class AirQualityDataModel extends Equatable {
|
||||
return AirQualityDataModel(
|
||||
date: DateTime.parse(json['date'] as String),
|
||||
data: (json['data'] as List<dynamic>)
|
||||
.map((e) => AirQualityPercentageData.fromJson(e as Map<String, dynamic>))
|
||||
.map(
|
||||
(e) => AirQualityPercentageData.fromJson(e as Map<String, dynamic>),
|
||||
)
|
||||
.toList(),
|
||||
);
|
||||
}
|
||||
@ -23,9 +25,9 @@ class AirQualityDataModel extends Equatable {
|
||||
static final Map<String, Color> metricColors = {
|
||||
'good': ColorsManager.goodGreen.withValues(alpha: 0.7),
|
||||
'moderate': ColorsManager.moderateYellow.withValues(alpha: 0.7),
|
||||
'poor': ColorsManager.poorOrange.withValues(alpha: 0.7),
|
||||
'unhealthy_sensitive': ColorsManager.poorOrange.withValues(alpha: 0.7),
|
||||
'unhealthy': ColorsManager.unhealthyRed.withValues(alpha: 0.7),
|
||||
'severe': ColorsManager.severePink.withValues(alpha: 0.7),
|
||||
'very_unhealthy': ColorsManager.severePink.withValues(alpha: 0.7),
|
||||
'hazardous': ColorsManager.hazardousPurple.withValues(alpha: 0.7),
|
||||
};
|
||||
|
||||
@ -36,22 +38,19 @@ class AirQualityDataModel extends Equatable {
|
||||
class AirQualityPercentageData extends Equatable {
|
||||
const AirQualityPercentageData({
|
||||
required this.type,
|
||||
required this.name,
|
||||
required this.percentage,
|
||||
});
|
||||
|
||||
final String type;
|
||||
final String name;
|
||||
final double percentage;
|
||||
|
||||
factory AirQualityPercentageData.fromJson(Map<String, dynamic> json) {
|
||||
return AirQualityPercentageData(
|
||||
type: json['type'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
type: json['type'] as String? ?? '',
|
||||
percentage: (json['percentage'] as num?)?.toDouble() ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
List<Object?> get props => [type, name, percentage];
|
||||
List<Object?> get props => [type, percentage];
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_date_picker_bloc/analytics_date_picker_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_devices/analytics_devices_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart';
|
||||
@ -22,6 +23,7 @@ abstract final class FetchAirQualityDataHelper {
|
||||
bool shouldFetchAnalyticsDevices = true,
|
||||
}) {
|
||||
final date = context.read<AnalyticsDatePickerBloc>().state.monthlyDate;
|
||||
final aqiType = context.read<AirQualityDistributionBloc>().state.selectedAqiType;
|
||||
loadAnalyticsDevices(
|
||||
context,
|
||||
communityUuid: communityUuid,
|
||||
@ -36,6 +38,7 @@ abstract final class FetchAirQualityDataHelper {
|
||||
context,
|
||||
spaceUuid: spaceUuid,
|
||||
date: date,
|
||||
aqiType: aqiType,
|
||||
);
|
||||
}
|
||||
|
||||
@ -104,10 +107,15 @@ abstract final class FetchAirQualityDataHelper {
|
||||
BuildContext context, {
|
||||
required String spaceUuid,
|
||||
required DateTime date,
|
||||
required AqiType aqiType,
|
||||
}) {
|
||||
context.read<AirQualityDistributionBloc>().add(
|
||||
LoadAirQualityDistribution(
|
||||
GetAirQualityDistributionParam(spaceUuid: spaceUuid, date: date),
|
||||
GetAirQualityDistributionParam(
|
||||
spaceUuid: spaceUuid,
|
||||
date: date,
|
||||
aqiType: aqiType,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -33,7 +33,7 @@ class AqiDistributionChartBox extends StatelessWidget {
|
||||
const Divider(),
|
||||
const SizedBox(height: 20),
|
||||
Expanded(
|
||||
child: AqiDistributionChart(chartData: state.filteredChartData),
|
||||
child: AqiDistributionChart(chartData: state.chartData),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
@ -34,14 +34,14 @@ class AqiDistributionChartTitle extends StatelessWidget {
|
||||
child: AqiTypeDropdown(
|
||||
onChanged: (value) {
|
||||
if (value != null) {
|
||||
final bloc = context.read<AirQualityDistributionBloc>();
|
||||
try {
|
||||
final bloc = context.read<AirQualityDistributionBloc>();
|
||||
final param = _makeLoadAqiDistributionParam(context, value);
|
||||
bloc
|
||||
..add(UpdateAqiTypeEvent(value))
|
||||
..add(LoadAirQualityDistribution(param));
|
||||
bloc.add(LoadAirQualityDistribution(param));
|
||||
} catch (_) {
|
||||
return;
|
||||
} finally {
|
||||
bloc.add(UpdateAqiTypeEvent(value));
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -16,7 +16,7 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real
|
||||
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/occupancy/blocs/occupancy/occupancy_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
|
||||
@ -109,7 +109,7 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
|
||||
),
|
||||
BlocProvider(
|
||||
create: (context) => AirQualityDistributionBloc(
|
||||
FakeAirQualityDistributionService(),
|
||||
RemoteAirQualityDistributionService(_httpService),
|
||||
),
|
||||
),
|
||||
BlocProvider(
|
||||
|
@ -20,7 +20,7 @@ class AnalyticsDateFilterButton extends StatefulWidget {
|
||||
final void Function(DateTime)? onDateSelected;
|
||||
final DatePickerType datePickerType;
|
||||
|
||||
static final _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||
static final Color _color = ColorsManager.blackColor.withValues(alpha: 0.8);
|
||||
|
||||
@override
|
||||
State<AnalyticsDateFilterButton> createState() =>
|
||||
@ -60,23 +60,21 @@ class _AnalyticsDateFilterButtonState extends State<AnalyticsDateFilterButton> {
|
||||
),
|
||||
),
|
||||
onPressed: () {
|
||||
showDialog(
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (_) {
|
||||
return switch (widget.datePickerType) {
|
||||
DatePickerType.month => MonthPickerWidget(
|
||||
selectedDate: widget.selectedDate,
|
||||
onDateSelected: (value) {
|
||||
widget.onDateSelected?.call(value);
|
||||
},
|
||||
),
|
||||
DatePickerType.year => YearPickerWidget(
|
||||
selectedDate: widget.selectedDate,
|
||||
onDateSelected: (value) {
|
||||
widget.onDateSelected?.call(value);
|
||||
},
|
||||
),
|
||||
};
|
||||
builder: (_) => switch (widget.datePickerType) {
|
||||
DatePickerType.month => MonthPickerWidget(
|
||||
selectedDate: widget.selectedDate,
|
||||
onDateSelected: (value) {
|
||||
widget.onDateSelected?.call(value);
|
||||
},
|
||||
),
|
||||
DatePickerType.year => YearPickerWidget(
|
||||
selectedDate: widget.selectedDate,
|
||||
onDateSelected: (value) {
|
||||
widget.onDateSelected?.call(value);
|
||||
},
|
||||
),
|
||||
},
|
||||
);
|
||||
},
|
||||
|
@ -1,9 +1,14 @@
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
|
||||
class GetAirQualityDistributionParam {
|
||||
final DateTime date;
|
||||
final String spaceUuid;
|
||||
final AqiType aqiType;
|
||||
|
||||
const GetAirQualityDistributionParam({
|
||||
const GetAirQualityDistributionParam(
|
||||
{
|
||||
required this.date,
|
||||
required this.spaceUuid,
|
||||
required this.aqiType,
|
||||
});
|
||||
}
|
||||
|
@ -1,95 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:syncrow_web/pages/analytics/models/air_quality_data_model.dart';
|
||||
import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart';
|
||||
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
|
||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||
|
||||
class FakeAirQualityDistributionService implements AirQualityDistributionService {
|
||||
final _random = Random();
|
||||
|
||||
@override
|
||||
Future<List<AirQualityDataModel>> getAirQualityDistribution(
|
||||
GetAirQualityDistributionParam param,
|
||||
) async {
|
||||
return Future.delayed(
|
||||
const Duration(milliseconds: 400),
|
||||
() => List.generate(30, (index) {
|
||||
final date = DateTime(2025, 5, 1).add(Duration(days: index));
|
||||
|
||||
final values = _generateRandomPercentages();
|
||||
final nullMask = List.generate(6, (_) => _shouldBeNull());
|
||||
|
||||
if (nullMask.every((isNull) => isNull)) {
|
||||
nullMask[_random.nextInt(6)] = false;
|
||||
}
|
||||
|
||||
final nonNullValues = _redistributePercentages(values, nullMask);
|
||||
|
||||
return AirQualityDataModel(
|
||||
date: date,
|
||||
data: [
|
||||
AirQualityPercentageData(
|
||||
type: AqiType.aqi.code,
|
||||
percentage: nonNullValues[0],
|
||||
name: 'good',
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'moderate',
|
||||
type: AqiType.co2.code,
|
||||
percentage: nonNullValues[1],
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'poor',
|
||||
percentage: nonNullValues[2],
|
||||
type: AqiType.hcho.code,
|
||||
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'unhealthy',
|
||||
percentage: nonNullValues[3],
|
||||
type: AqiType.pm10.code,
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'severe',
|
||||
type: AqiType.pm25.code,
|
||||
percentage: nonNullValues[4],
|
||||
),
|
||||
AirQualityPercentageData(
|
||||
name: 'hazardous',
|
||||
percentage: nonNullValues[5],
|
||||
type: AqiType.co2.code,
|
||||
),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
List<double> _redistributePercentages(
|
||||
List<double> originalValues,
|
||||
List<bool> nullMask,
|
||||
) {
|
||||
double nonNullSum = 0;
|
||||
for (int i = 0; i < originalValues.length; i++) {
|
||||
if (!nullMask[i]) {
|
||||
nonNullSum += originalValues[i];
|
||||
}
|
||||
}
|
||||
|
||||
return List.generate(originalValues.length, (i) {
|
||||
if (nullMask[i]) return 0;
|
||||
return (originalValues[i] / nonNullSum * 100).roundToDouble();
|
||||
});
|
||||
}
|
||||
|
||||
bool _shouldBeNull() => _random.nextDouble() < 0.6;
|
||||
|
||||
List<double> _generateRandomPercentages() {
|
||||
final values = List.generate(6, (_) => _random.nextDouble());
|
||||
|
||||
final sum = values.reduce((a, b) => a + b);
|
||||
|
||||
return values.map((value) => (value / sum * 100).roundToDouble()).toList();
|
||||
}
|
||||
}
|
@ -3,7 +3,8 @@ import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_
|
||||
import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/air_quality_distribution_service.dart';
|
||||
import 'package:syncrow_web/services/api/http_service.dart';
|
||||
|
||||
class RemoteAirQualityDistributionService implements AirQualityDistributionService {
|
||||
final class RemoteAirQualityDistributionService
|
||||
implements AirQualityDistributionService {
|
||||
RemoteAirQualityDistributionService(this._httpService);
|
||||
|
||||
final HTTPService _httpService;
|
||||
@ -14,10 +15,10 @@ class RemoteAirQualityDistributionService implements AirQualityDistributionServi
|
||||
) async {
|
||||
try {
|
||||
final response = await _httpService.get(
|
||||
path: 'endpoint',
|
||||
path: '/aqi/distribution/space/${param.spaceUuid}',
|
||||
queryParameters: {
|
||||
'spaceUuid': param.spaceUuid,
|
||||
'date': param.date.toIso8601String(),
|
||||
'monthDate': _formatDate(param.date),
|
||||
'pollutantType': param.aqiType.code,
|
||||
},
|
||||
expectedResponseModel: (data) {
|
||||
final json = data as Map<String, dynamic>? ?? {};
|
||||
@ -33,4 +34,8 @@ class RemoteAirQualityDistributionService implements AirQualityDistributionServi
|
||||
throw Exception('Failed to load energy consumption per phase: $e');
|
||||
}
|
||||
}
|
||||
|
||||
static String _formatDate(DateTime date) {
|
||||
return '${date.year}-${date.month.toString().padLeft(2, '0')}';
|
||||
}
|
||||
}
|
||||
|
@ -26,15 +26,15 @@ class DeviceLocationDetailsServiceDecorator implements DeviceLocationService {
|
||||
if (data != null) {
|
||||
final addressData = data['address'] as Map<String, dynamic>;
|
||||
return deviceLocationInfo.copyWith(
|
||||
city: addressData['city'],
|
||||
country: addressData['country_code'].toString().toUpperCase(),
|
||||
address: addressData['state'],
|
||||
city: addressData['city'] as String?,
|
||||
country: addressData['country_code']?.toString().toUpperCase(),
|
||||
address: addressData['state'] as String?,
|
||||
);
|
||||
}
|
||||
|
||||
return deviceLocationInfo;
|
||||
} catch (e) {
|
||||
throw Exception('Failed to load device location info: ${e.toString()}');
|
||||
throw Exception('Failed to load device location info: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +0,0 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
// clang-format off
|
||||
|
||||
#ifndef GENERATED_PLUGIN_REGISTRANT_
|
||||
#define GENERATED_PLUGIN_REGISTRANT_
|
||||
|
||||
#include <flutter_linux/flutter_linux.h>
|
||||
|
||||
// Registers Flutter plugins.
|
||||
void fl_register_plugins(FlPluginRegistry* registry);
|
||||
|
||||
#endif // GENERATED_PLUGIN_REGISTRANT_
|
@ -1,26 +0,0 @@
|
||||
//
|
||||
// Generated file. Do not edit.
|
||||
//
|
||||
|
||||
import FlutterMacOS
|
||||
import Foundation
|
||||
|
||||
import firebase_analytics
|
||||
import firebase_core
|
||||
import firebase_crashlytics
|
||||
import firebase_database
|
||||
import flutter_secure_storage_macos
|
||||
import path_provider_foundation
|
||||
import shared_preferences_foundation
|
||||
import url_launcher_macos
|
||||
|
||||
func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||
FirebaseAnalyticsPlugin.register(with: registry.registrar(forPlugin: "FirebaseAnalyticsPlugin"))
|
||||
FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin"))
|
||||
FLTFirebaseCrashlyticsPlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCrashlyticsPlugin"))
|
||||
FLTFirebaseDatabasePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseDatabasePlugin"))
|
||||
FlutterSecureStoragePlugin.register(with: registry.registrar(forPlugin: "FlutterSecureStoragePlugin"))
|
||||
PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin"))
|
||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||
}
|
Reference in New Issue
Block a user