From 5591c78d884ad0610d86fc816aab98ab7afe0d60 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 16 Jun 2025 09:29:31 +0300 Subject: [PATCH 1/8] Refactor RemoteRangeOfAqiService to update API endpoint to match what the actual endpoint is. --- .../range_of_aqi/remote_range_of_aqi_service.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/pages/analytics/services/range_of_aqi/remote_range_of_aqi_service.dart b/lib/pages/analytics/services/range_of_aqi/remote_range_of_aqi_service.dart index 1a80ef33..642ad400 100644 --- a/lib/pages/analytics/services/range_of_aqi/remote_range_of_aqi_service.dart +++ b/lib/pages/analytics/services/range_of_aqi/remote_range_of_aqi_service.dart @@ -12,11 +12,8 @@ final class RemoteRangeOfAqiService implements RangeOfAqiService { Future> load(GetRangeOfAqiParam param) async { try { final response = await _httpService.get( - path: 'endpoint', - queryParameters: { - 'spaceUuid': param.spaceUuid, - 'date': param.date.toIso8601String(), - }, + path: '/aqi/range/space/${param.spaceUuid}', + queryParameters: {'monthDate': _formatDate(param.date)}, expectedResponseModel: (data) { final json = data as Map? ?? {}; final mappedData = json['data'] as List? ?? []; @@ -28,7 +25,11 @@ final class RemoteRangeOfAqiService implements RangeOfAqiService { ); return response; } catch (e) { - throw Exception('Failed to load energy consumption per phase: $e'); + throw Exception('Failed to load range of aqi: $e'); } } + + static String _formatDate(DateTime date) { + return '${date.year}-${date.month.toString().padLeft(2, '0')}'; + } } From 131682095461aae55f58f8f8e0b1dc67936c7881 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 16 Jun 2025 09:30:39 +0300 Subject: [PATCH 2/8] Injected `RemoteRangeOfAqiService` into `RangeOfAqiBloc` instead of using `FakeRangeOfAqiService`, because the API is ready. --- .../analytics/modules/analytics/views/analytics_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index 1ecd9aa3..903b0325 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -27,7 +27,7 @@ import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_devi import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart'; import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/remote_occupancy_heat_map_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/range_of_aqi/fake_range_of_aqi_service.dart'; +import 'package:syncrow_web/pages/analytics/services/range_of_aqi/remote_range_of_aqi_service.dart'; import 'package:syncrow_web/pages/analytics/services/realtime_device_service/firebase_realtime_device_service.dart'; import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/remote_total_energy_consumption_service.dart'; import 'package:syncrow_web/pages/device_managment/shared/navigate_home_grid_view.dart'; @@ -104,7 +104,7 @@ class _AnalyticsPageState extends State { ), BlocProvider( create: (context) => RangeOfAqiBloc( - FakeRangeOfAqiService(), + RemoteRangeOfAqiService(_httpService), ), ), BlocProvider( From a0d9819532fdf6a598f3aad0c4f99604b56b1f46 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 16 Jun 2025 09:30:50 +0300 Subject: [PATCH 3/8] Deleted `FakeRangeOfAqiService`. --- .../widgets/range_of_aqi_chart.dart | 2 +- .../fake_range_of_aqi_service.dart | 36 ------------------- 2 files changed, 1 insertion(+), 37 deletions(-) delete mode 100644 lib/pages/analytics/services/range_of_aqi/fake_range_of_aqi_service.dart diff --git a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart.dart b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart.dart index fc63e413..5e731d90 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart.dart @@ -63,7 +63,7 @@ class RangeOfAqiChart extends StatelessWidget { gradient: LinearGradient( begin: Alignment.bottomCenter, end: Alignment.topCenter, - stops: [0.0, 0.2, 0.4, 0.6, 0.8, 1.0], + stops: const [0.0, 0.2, 0.4, 0.6, 0.8, 1.0], colors: RangeOfAqiChartsHelper.gradientData.map((e) { final (color, _) = e; return color.withValues(alpha: 0.6); diff --git a/lib/pages/analytics/services/range_of_aqi/fake_range_of_aqi_service.dart b/lib/pages/analytics/services/range_of_aqi/fake_range_of_aqi_service.dart deleted file mode 100644 index 01ad6fa1..00000000 --- a/lib/pages/analytics/services/range_of_aqi/fake_range_of_aqi_service.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:syncrow_web/pages/analytics/models/range_of_aqi.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart'; -import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart'; -import 'package:syncrow_web/pages/analytics/services/range_of_aqi/range_of_aqi_service.dart'; - -class FakeRangeOfAqiService implements RangeOfAqiService { - @override - Future> load(GetRangeOfAqiParam param) async { - return await Future.delayed(const Duration(milliseconds: 800), () { - final random = DateTime.now().millisecondsSinceEpoch; - - return List.generate(30, (index) { - final date = DateTime(2025, 5, 1).add(Duration(days: index)); - - final min = ((random + index * 17) % 200).toDouble(); - final avgDelta = ((random + index * 23) % 50).toDouble() + 20; - final maxDelta = ((random + index * 31) % 50).toDouble() + 30; - - final avg = (min + avgDelta).clamp(0.0, 301.0); - final max = (avg + maxDelta).clamp(0.0, 301.0); - - return RangeOfAqi( - data: [ - RangeOfAqiValue(type: AqiType.aqi.code, min: min, average: avg, max: max), - RangeOfAqiValue(type: AqiType.pm25.code, min: min, average: avg, max: max), - RangeOfAqiValue(type: AqiType.pm10.code, min: min, average: avg, max: max), - RangeOfAqiValue(type: AqiType.hcho.code, min: min, average: avg, max: max), - RangeOfAqiValue(type: AqiType.tvoc.code, min: min, average: avg, max: max), - RangeOfAqiValue(type: AqiType.co2.code, min: min, average: avg, max: max), - ], - date: date, - ); - }); - }); - } -} From 89e12e47da5ca56504d5d6feb68b50c620b380e0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 16 Jun 2025 10:28:26 +0300 Subject: [PATCH 4/8] ajusted `AqiType.code`s to match the api. --- .../modules/air_quality/widgets/aqi_type_dropdown.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart index 60a686ff..5d482d9c 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart @@ -6,8 +6,8 @@ enum AqiType { aqi('AQI', '', 'aqi'), pm25('PM2.5', 'µg/m³', 'pm25'), pm10('PM10', 'µg/m³', 'pm10'), - hcho('HCHO', 'mg/m³', 'hcho'), - tvoc('TVOC', 'µg/m³', 'tvoc'), + hcho('HCHO', 'mg/m³', 'cho2'), + tvoc('TVOC', 'µg/m³', 'voc'), co2('CO2', 'ppm', 'co2'); const AqiType(this.value, this.unit, this.code); From 312d185932ed32520ea0651b39478e77aa7fb843 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 16 Jun 2025 10:29:03 +0300 Subject: [PATCH 5/8] unsort all data from `AqiDistributionChart` since the api returns it sorted, and the pacakge handles sorting. --- .../widgets/aqi_distribution_chart.dart | 46 ++++++++----------- 1 file changed, 18 insertions(+), 28 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart index 373e36ca..2f3d7ff0 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart.dart @@ -16,11 +16,6 @@ class AqiDistributionChart extends StatelessWidget { @override Widget build(BuildContext context) { - final sortedData = List.from(chartData) - ..sort( - (a, b) => a.date.compareTo(b.date), - ); - return BarChart( BarChartData( maxY: 100.1, @@ -30,29 +25,25 @@ class AqiDistributionChart extends StatelessWidget { borderData: EnergyManagementChartsHelper.borderData(), barTouchData: _barTouchData(context), titlesData: _titlesData(context), - barGroups: _buildBarGroups(sortedData), + barGroups: _buildBarGroups(), ), duration: Duration.zero, ); } - List _buildBarGroups(List sortedData) { - return List.generate(sortedData.length, (index) { - final data = sortedData[index]; + List _buildBarGroups() { + return List.generate(chartData.length, (index) { + final data = chartData[index]; final stackItems = []; double currentY = 0; - bool isFirstElement = true; + var isFirstElement = true; - // Sort data by type to ensure consistent order - final sortedPercentageData = List.from(data.data) - ..sort((a, b) => a.type.compareTo(b.type)); - - for (final percentageData in sortedPercentageData) { + for (final percentageData in data.data) { stackItems.add( BarChartRodData( fromY: currentY, - toY: currentY + percentageData.percentage , - color: AirQualityDataModel.metricColors[percentageData.name]!, + toY: currentY + percentageData.percentage, + color: AirQualityDataModel.metricColors[percentageData.type], borderRadius: isFirstElement ? const BorderRadius.only( topLeft: Radius.circular(22), @@ -84,23 +75,21 @@ class AqiDistributionChart extends StatelessWidget { tooltipRoundedRadius: 16, tooltipPadding: const EdgeInsets.all(8), getTooltipItem: (group, groupIndex, rod, rodIndex) { - final data = chartData[group.x.toInt()]; + final data = chartData[group.x]; - final List children = []; + final children = []; final textStyle = context.textTheme.bodySmall?.copyWith( color: ColorsManager.blackColor, - fontSize: 12, + fontSize: 8, ); - // Sort data by type to ensure consistent order - final sortedPercentageData = List.from(data.data) - ..sort((a, b) => a.type.compareTo(b.type)); - - for (final percentageData in sortedPercentageData) { + for (final percentageData in data.data) { + final percentage = percentageData.percentage.toStringAsFixed(1); + final type = percentageData.type[0].toUpperCase() + + percentageData.type.substring(1).replaceAll('_', ' '); children.add(TextSpan( - text: - '\n${percentageData.type.toUpperCase()}: ${percentageData.percentage.toStringAsFixed(1)}%', + text: '\n$type: $percentage%', style: textStyle, )); } @@ -109,9 +98,10 @@ class AqiDistributionChart extends StatelessWidget { DateFormat('dd/MM/yyyy').format(data.date), context.textTheme.bodyMedium!.copyWith( color: ColorsManager.blackColor, - fontSize: 16, + fontSize: 9, fontWeight: FontWeight.w600, ), + textAlign: TextAlign.start, children: children, ); }, From 362557d0d0d70da676cd67a32761fd81abaefc19 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 16 Jun 2025 10:29:31 +0300 Subject: [PATCH 6/8] removed filtered data from`AirQualityDistributionBloc` since it isnt needed for this bloc. --- .../air_quality_distribution_bloc.dart | 21 +------------------ .../air_quality_distribution_state.dart | 4 ---- 2 files changed, 1 insertion(+), 24 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart index fb7e2352..40d51d2b 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart @@ -33,7 +33,6 @@ class AirQualityDistributionBloc state.copyWith( status: AirQualityDistributionStatus.success, chartData: result, - filteredChartData: _arrangeChartDataByType(result, state.selectedAqiType), ), ); } catch (e) { @@ -58,24 +57,6 @@ class AirQualityDistributionBloc UpdateAqiTypeEvent event, Emitter emit, ) { - emit( - state.copyWith( - selectedAqiType: event.aqiType, - filteredChartData: _arrangeChartDataByType(state.chartData, event.aqiType), - ), - ); - } - - List _arrangeChartDataByType( - List data, - AqiType aqiType, - ) { - final filteredData = data.map( - (data) => AirQualityDataModel( - date: data.date, - data: data.data.where((value) => value.type == aqiType.code).toList(), - ), - ); - return filteredData.toList(); + emit(state.copyWith(selectedAqiType: event.aqiType)); } } diff --git a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_state.dart b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_state.dart index 65665882..0b02fd7e 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_state.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_state.dart @@ -11,28 +11,24 @@ class AirQualityDistributionState extends Equatable { const AirQualityDistributionState({ this.status = AirQualityDistributionStatus.initial, this.chartData = const [], - this.filteredChartData = const [], this.errorMessage, this.selectedAqiType = AqiType.aqi, }); final AirQualityDistributionStatus status; final List chartData; - final List filteredChartData; final String? errorMessage; final AqiType selectedAqiType; AirQualityDistributionState copyWith({ AirQualityDistributionStatus? status, List? chartData, - List? filteredChartData, String? errorMessage, AqiType? selectedAqiType, }) { return AirQualityDistributionState( status: status ?? this.status, chartData: chartData ?? this.chartData, - filteredChartData: filteredChartData ?? this.filteredChartData, errorMessage: errorMessage ?? this.errorMessage, selectedAqiType: selectedAqiType ?? this.selectedAqiType, ); From 5f20d52e57af718ae22d8cc66bd61a8ea798d00a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 16 Jun 2025 10:46:48 +0300 Subject: [PATCH 7/8] doesnt load aqi data when spaceUuid is empty. --- .../widgets/aqi_distribution_chart_title.dart | 30 +++++++++++++++++-- .../analytics_page_tabs_and_children.dart | 6 ++-- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart index e32043c5..4cf79263 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart @@ -2,8 +2,11 @@ import 'package:flutter/material.dart'; 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/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/energy_management/widgets/chart_title.dart'; +import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart'; import 'package:syncrow_web/pages/analytics/widgets/charts_loading_widget.dart'; +import 'package:syncrow_web/pages/space_tree/bloc/space_tree_bloc.dart'; class AqiDistributionChartTitle extends StatelessWidget { const AqiDistributionChartTitle({required this.isLoading, super.key}); @@ -31,9 +34,15 @@ class AqiDistributionChartTitle extends StatelessWidget { child: AqiTypeDropdown( onChanged: (value) { if (value != null) { - context - .read() - .add(UpdateAqiTypeEvent(value)); + try { + final bloc = context.read(); + final param = _makeLoadAqiDistributionParam(context, value); + bloc + ..add(UpdateAqiTypeEvent(value)) + ..add(LoadAirQualityDistribution(param)); + } catch (_) { + return; + } } }, ), @@ -41,4 +50,19 @@ class AqiDistributionChartTitle extends StatelessWidget { ], ); } + + GetAirQualityDistributionParam _makeLoadAqiDistributionParam( + BuildContext context, + AqiType aqiType, + ) { + final date = context.read().state.monthlyDate; + final spaceUuid = + context.read().state.selectedSpaces.firstOrNull ?? ''; + if (spaceUuid.isEmpty) throw Exception('Space UUID is empty'); + return GetAirQualityDistributionParam( + date: date, + spaceUuid: spaceUuid, + aqiType: aqiType, + ); + } } diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart index f6197e46..13501c19 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_page_tabs_and_children.dart @@ -118,7 +118,7 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { communityUuid: communities.firstOrNull ?? '', spaceUuid: spaces.firstOrNull ?? '', ); - break; + return; case AnalyticsPageTab.airQuality: _onAirQualityDateChanged( context, @@ -126,8 +126,9 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { communityUuid: communities.firstOrNull ?? '', spaceUuid: spaces.firstOrNull ?? '', ); + return; default: - break; + return; } } } @@ -157,6 +158,7 @@ class AnalyticsPageTabsAndChildren extends StatelessWidget { required String communityUuid, required String spaceUuid, }) { + if (spaceUuid.isEmpty) return; FetchAirQualityDataHelper.loadAirQualityData( context, date: date, From 6ff9c602f15f51f4f756a5a4574cbab38dd77806 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Mon, 16 Jun 2025 10:59:51 +0300 Subject: [PATCH 8/8] SP-1723-FE-Integrate-Charts-with-API-s-for-AQI-sensor. --- .../models/air_quality_data_model.dart | 15 ++- .../fetch_air_quality_data_helper.dart | 10 +- .../widgets/aqi_distribution_chart_box.dart | 2 +- .../widgets/aqi_distribution_chart_title.dart | 8 +- .../analytics/views/analytics_page.dart | 4 +- .../widgets/analytics_date_filter_button.dart | 32 +++---- .../get_air_quality_distribution_param.dart | 7 +- ...fake_air_quality_distribution_service.dart | 95 ------------------- ...mote_air_quality_distribution_service.dart | 13 ++- ...ce_location_details_service_decorator.dart | 8 +- linux/flutter/generated_plugin_registrant.h | 15 --- macos/Flutter/GeneratedPluginRegistrant.swift | 26 ----- 12 files changed, 57 insertions(+), 178 deletions(-) delete mode 100644 lib/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart delete mode 100644 linux/flutter/generated_plugin_registrant.h delete mode 100644 macos/Flutter/GeneratedPluginRegistrant.swift diff --git a/lib/pages/analytics/models/air_quality_data_model.dart b/lib/pages/analytics/models/air_quality_data_model.dart index 2eab2ddb..38b72abc 100644 --- a/lib/pages/analytics/models/air_quality_data_model.dart +++ b/lib/pages/analytics/models/air_quality_data_model.dart @@ -15,7 +15,9 @@ class AirQualityDataModel extends Equatable { return AirQualityDataModel( date: DateTime.parse(json['date'] as String), data: (json['data'] as List) - .map((e) => AirQualityPercentageData.fromJson(e as Map)) + .map( + (e) => AirQualityPercentageData.fromJson(e as Map), + ) .toList(), ); } @@ -23,9 +25,9 @@ class AirQualityDataModel extends Equatable { static final Map 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 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 get props => [type, name, percentage]; + List get props => [type, percentage]; } diff --git a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart index cb37484c..223c0357 100644 --- a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart +++ b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart @@ -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().state.monthlyDate; + final aqiType = context.read().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().add( LoadAirQualityDistribution( - GetAirQualityDistributionParam(spaceUuid: spaceUuid, date: date), + GetAirQualityDistributionParam( + spaceUuid: spaceUuid, + date: date, + aqiType: aqiType, + ), ), ); } diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart index 8a57fe0b..25cfd19d 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart @@ -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), ), ], ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart index 4cf79263..926d28e1 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_title.dart @@ -34,14 +34,14 @@ class AqiDistributionChartTitle extends StatelessWidget { child: AqiTypeDropdown( onChanged: (value) { if (value != null) { + final bloc = context.read(); try { - final bloc = context.read(); final param = _makeLoadAqiDistributionParam(context, value); - bloc - ..add(UpdateAqiTypeEvent(value)) - ..add(LoadAirQualityDistribution(param)); + bloc.add(LoadAirQualityDistribution(param)); } catch (_) { return; + } finally { + bloc.add(UpdateAqiTypeEvent(value)); } } }, diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index 903b0325..3d8b1eb3 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -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 { ), BlocProvider( create: (context) => AirQualityDistributionBloc( - FakeAirQualityDistributionService(), + RemoteAirQualityDistributionService(_httpService), ), ), BlocProvider( diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart index af70cd86..616688dd 100644 --- a/lib/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart +++ b/lib/pages/analytics/modules/analytics/widgets/analytics_date_filter_button.dart @@ -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 createState() => @@ -60,23 +60,21 @@ class _AnalyticsDateFilterButtonState extends State { ), ), onPressed: () { - showDialog( + showDialog( 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); + }, + ), }, ); }, diff --git a/lib/pages/analytics/params/get_air_quality_distribution_param.dart b/lib/pages/analytics/params/get_air_quality_distribution_param.dart index f1d3fe9f..ecad66b0 100644 --- a/lib/pages/analytics/params/get_air_quality_distribution_param.dart +++ b/lib/pages/analytics/params/get_air_quality_distribution_param.dart @@ -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, }); } diff --git a/lib/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart b/lib/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart deleted file mode 100644 index e0023f53..00000000 --- a/lib/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart +++ /dev/null @@ -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> 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 _redistributePercentages( - List originalValues, - List 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 _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(); - } -} diff --git a/lib/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart b/lib/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart index dcf00600..49755f7b 100644 --- a/lib/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart +++ b/lib/pages/analytics/services/air_quality_distribution/remote_air_quality_distribution_service.dart @@ -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? ?? {}; @@ -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')}'; + } } diff --git a/lib/pages/analytics/services/device_location/device_location_details_service_decorator.dart b/lib/pages/analytics/services/device_location/device_location_details_service_decorator.dart index 0239bcb7..f38f607d 100644 --- a/lib/pages/analytics/services/device_location/device_location_details_service_decorator.dart +++ b/lib/pages/analytics/services/device_location/device_location_details_service_decorator.dart @@ -26,15 +26,15 @@ class DeviceLocationDetailsServiceDecorator implements DeviceLocationService { if (data != null) { final addressData = data['address'] as Map; 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'); } } } diff --git a/linux/flutter/generated_plugin_registrant.h b/linux/flutter/generated_plugin_registrant.h deleted file mode 100644 index e0f0a47b..00000000 --- a/linux/flutter/generated_plugin_registrant.h +++ /dev/null @@ -1,15 +0,0 @@ -// -// Generated file. Do not edit. -// - -// clang-format off - -#ifndef GENERATED_PLUGIN_REGISTRANT_ -#define GENERATED_PLUGIN_REGISTRANT_ - -#include - -// Registers Flutter plugins. -void fl_register_plugins(FlPluginRegistry* registry); - -#endif // GENERATED_PLUGIN_REGISTRANT_ diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift deleted file mode 100644 index 585688ef..00000000 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ /dev/null @@ -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")) -}