diff --git a/lib/pages/analytics/models/range_of_aqi.dart b/lib/pages/analytics/models/range_of_aqi.dart index 759666c2..4cee813e 100644 --- a/lib/pages/analytics/models/range_of_aqi.dart +++ b/lib/pages/analytics/models/range_of_aqi.dart @@ -1,18 +1,31 @@ import 'package:equatable/equatable.dart'; class RangeOfAqi extends Equatable { - final double min; - final double avg; - final double max; final DateTime date; + final List data; const RangeOfAqi({ - required this.min, - required this.avg, - required this.max, + required this.data, required this.date, }); @override - List get props => [min, avg, max, date]; + List get props => [data, date]; +} + +class RangeOfAqiValue extends Equatable { + final String type; + final double min; + final double average; + final double max; + + const RangeOfAqiValue({ + required this.type, + required this.min, + required this.average, + required this.max, + }); + + @override + List get props => [type, min, average, max]; } diff --git a/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart b/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart index febbcf58..88c3715e 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.dart @@ -1,6 +1,7 @@ import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; 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'; @@ -11,6 +12,7 @@ class RangeOfAqiBloc extends Bloc { RangeOfAqiBloc(this._rangeOfAqiService) : super(const RangeOfAqiState()) { on(_onLoadRangeOfAqiEvent); on(_onClearRangeOfAqiEvent); + on(_onUpdateAqiTypeEvent); } final RangeOfAqiService _rangeOfAqiService; @@ -20,19 +22,55 @@ class RangeOfAqiBloc extends Bloc { Emitter emit, ) async { emit( - RangeOfAqiState( - status: RangeOfAqiStatus.loading, - rangeOfAqi: state.rangeOfAqi, - ), + state.copyWith(status: RangeOfAqiStatus.loading), ); try { final rangeOfAqi = await _rangeOfAqiService.load(event.param); - emit(RangeOfAqiState(status: RangeOfAqiStatus.loaded, rangeOfAqi: rangeOfAqi)); + emit( + state.copyWith( + status: RangeOfAqiStatus.loaded, + rangeOfAqi: rangeOfAqi, + filteredRangeOfAqi: _arrangeChartDataByType( + rangeOfAqi, + state.selectedAqiType, + ), + ), + ); } catch (e) { - emit(RangeOfAqiState(status: RangeOfAqiStatus.failure, errorMessage: '$e')); + emit( + state.copyWith( + status: RangeOfAqiStatus.failure, + errorMessage: '$e', + ), + ); } } + void _onUpdateAqiTypeEvent( + UpdateAqiTypeEvent event, + Emitter emit, + ) { + emit( + state.copyWith( + selectedAqiType: event.aqiType, + filteredRangeOfAqi: _arrangeChartDataByType(state.rangeOfAqi, event.aqiType), + ), + ); + } + + List _arrangeChartDataByType( + List rangeOfAqi, + AqiType aqiType, + ) { + final filteredRangeOfAqi = rangeOfAqi.map( + (data) => RangeOfAqi( + date: data.date, + data: data.data.where((value) => value.type == aqiType.code).toList(), + ), + ); + return filteredRangeOfAqi.toList(); + } + void _onClearRangeOfAqiEvent( ClearRangeOfAqiEvent event, Emitter emit, diff --git a/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_event.dart b/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_event.dart index 8a429587..6a08df5b 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_event.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_event.dart @@ -16,6 +16,15 @@ class LoadRangeOfAqiEvent extends RangeOfAqiEvent { List get props => [param]; } +class UpdateAqiTypeEvent extends RangeOfAqiEvent { + const UpdateAqiTypeEvent(this.aqiType); + + final AqiType aqiType; + + @override + List get props => [aqiType]; +} + class ClearRangeOfAqiEvent extends RangeOfAqiEvent { const ClearRangeOfAqiEvent(); } diff --git a/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_state.dart b/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_state.dart index 392e98c1..9308020c 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_state.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_state.dart @@ -5,14 +5,35 @@ enum RangeOfAqiStatus { initial, loading, loaded, failure } final class RangeOfAqiState extends Equatable { const RangeOfAqiState({ this.rangeOfAqi = const [], + this.filteredRangeOfAqi = const [], this.status = RangeOfAqiStatus.initial, this.errorMessage, + this.selectedAqiType = AqiType.aqi, }); final RangeOfAqiStatus status; final List rangeOfAqi; + final List filteredRangeOfAqi; final String? errorMessage; + final AqiType selectedAqiType; + + RangeOfAqiState copyWith({ + RangeOfAqiStatus? status, + List? rangeOfAqi, + List? filteredRangeOfAqi, + String? errorMessage, + AqiType? selectedAqiType, + }) { + return RangeOfAqiState( + status: status ?? this.status, + rangeOfAqi: rangeOfAqi ?? this.rangeOfAqi, + filteredRangeOfAqi: filteredRangeOfAqi ?? this.filteredRangeOfAqi, + errorMessage: errorMessage ?? this.errorMessage, + selectedAqiType: selectedAqiType ?? this.selectedAqiType, + ); + } @override - List get props => [status, rangeOfAqi, errorMessage]; + List get props => + [status, rangeOfAqi, filteredRangeOfAqi, errorMessage, selectedAqiType]; } 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 55de65d3..1919f518 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 @@ -2,7 +2,6 @@ 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/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'; @@ -28,7 +27,6 @@ abstract final class FetchAirQualityDataHelper { context, spaceUuid: spaceUuid, date: date, - aqiType: AqiType.aqi, ); loadAirQualityDistribution( context, @@ -76,14 +74,12 @@ abstract final class FetchAirQualityDataHelper { BuildContext context, { required String spaceUuid, required DateTime date, - required AqiType aqiType, }) { context.read().add( LoadRangeOfAqiEvent( GetRangeOfAqiParam( date: date, spaceUuid: spaceUuid, - aqiType: aqiType, ), ), ); 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 c725d1fa..60a686ff 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 @@ -3,17 +3,18 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; enum AqiType { - aqi('AQI', ''), - pm25('PM2.5', 'µg/m³'), - pm10('PM10', 'µg/m³'), - hcho('HCHO', 'mg/m³'), - tvoc('TVOC', 'µg/m³'), - co2('CO2', 'ppm'); + aqi('AQI', '', 'aqi'), + pm25('PM2.5', 'µg/m³', 'pm25'), + pm10('PM10', 'µg/m³', 'pm10'), + hcho('HCHO', 'mg/m³', 'hcho'), + tvoc('TVOC', 'µg/m³', 'tvoc'), + co2('CO2', 'ppm', 'co2'); - const AqiType(this.value, this.unit); + const AqiType(this.value, this.unit, this.code); final String value; final String unit; + final String code; } class AqiTypeDropdown extends StatefulWidget { 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 08a036c0..fc63e413 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 @@ -13,23 +13,37 @@ class RangeOfAqiChart extends StatelessWidget { required this.chartData, }); - List<(List values, Color color, Color? dotColor)> get _lines => [ - ( - chartData.map((e) => e.max).toList(), - ColorsManager.maxPurple, - ColorsManager.maxPurpleDot, - ), - ( - chartData.map((e) => e.avg).toList(), - Colors.white, - null, - ), - ( - chartData.map((e) => e.min).toList(), - ColorsManager.minBlue, - ColorsManager.minBlueDot, - ), - ]; + List<(List values, Color color, Color? dotColor)> get _lines { + final sortedData = List.from(chartData) + ..sort((a, b) => a.date.compareTo(b.date)); + + return [ + ( + sortedData.map((e) { + final value = e.data.firstOrNull; + return value?.max ?? 0; + }).toList(), + ColorsManager.maxPurple, + ColorsManager.maxPurpleDot, + ), + ( + sortedData.map((e) { + final value = e.data.firstOrNull; + return value?.average ?? 0; + }).toList(), + Colors.white, + null, + ), + ( + sortedData.map((e) { + final value = e.data.firstOrNull; + return value?.min ?? 0; + }).toList(), + ColorsManager.minBlue, + ColorsManager.minBlueDot, + ), + ]; + } @override Widget build(BuildContext context) { diff --git a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart index 0fe4c4bd..fefb7a9c 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_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/range_of_aqi_chart.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart'; +import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; import 'package:syncrow_web/utils/style.dart'; @@ -26,13 +27,23 @@ class RangeOfAqiChartBox extends StatelessWidget { AnalyticsErrorWidget(state.errorMessage), const SizedBox(height: 10), ], - RangeOfAqiChartTitle( - isLoading: state.status == RangeOfAqiStatus.loading, + GestureDetector( + onTap: () { + context.read().add(LoadRangeOfAqiEvent( + GetRangeOfAqiParam( + spaceUuid: '123', + date: DateTime.now().subtract(const Duration(days: 30)), + ), + )); + }, + child: RangeOfAqiChartTitle( + isLoading: state.status == RangeOfAqiStatus.loading, + ), ), const SizedBox(height: 10), const Divider(), const SizedBox(height: 20), - Expanded(child: RangeOfAqiChart(chartData: state.rangeOfAqi)), + Expanded(child: RangeOfAqiChart(chartData: state.filteredRangeOfAqi)), ], ), ); diff --git a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart index 04cefd6c..6c7aa235 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_title.dart @@ -1,15 +1,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.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/widgets/chart_informative_cell.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/chart_title.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 RangeOfAqiChartTitle extends StatelessWidget { - const RangeOfAqiChartTitle({required this.isLoading, super.key}); + const RangeOfAqiChartTitle({ + required this.isLoading, + super.key, + }); + final bool isLoading; static const List<(Color color, String title, bool hasBorder)> _colors = [ @@ -59,19 +62,16 @@ class RangeOfAqiChartTitle extends StatelessWidget { child: FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerEnd, - child: AqiTypeDropdown( + child: AqiTypeDropdown( onChanged: (value) { final spaceTreeState = context.read().state; final spaceUuid = spaceTreeState.selectedSpaces.firstOrNull; - if (spaceUuid == null) return; + // if (spaceUuid == null) return; - FetchAirQualityDataHelper.loadRangeOfAqi( - context, - spaceUuid: spaceUuid, - date: context.read().state.monthlyDate, - aqiType: value ?? AqiType.aqi, - ); + if (value != null) { + context.read().add(UpdateAqiTypeEvent(value)); + } }, ), ), diff --git a/lib/pages/analytics/params/get_range_of_aqi_param.dart b/lib/pages/analytics/params/get_range_of_aqi_param.dart index bbf24658..ef53fe76 100644 --- a/lib/pages/analytics/params/get_range_of_aqi_param.dart +++ b/lib/pages/analytics/params/get_range_of_aqi_param.dart @@ -1,16 +1,12 @@ import 'package:equatable/equatable.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart'; class GetRangeOfAqiParam extends Equatable { final DateTime date; final String spaceUuid; - final AqiType aqiType; - const GetRangeOfAqiParam( - { + const GetRangeOfAqiParam({ required this.date, required this.spaceUuid, - required this.aqiType, }); @override 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 index 13173c94..01ad6fa1 100644 --- 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 @@ -1,4 +1,5 @@ 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'; @@ -18,10 +19,15 @@ class FakeRangeOfAqiService implements RangeOfAqiService { final avg = (min + avgDelta).clamp(0.0, 301.0); final max = (avg + maxDelta).clamp(0.0, 301.0); - return RangeOfAqi( - min: min, - avg: avg, - max: max, + 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, ); });