diff --git a/lib/pages/analytics/models/range_of_aqi.dart b/lib/pages/analytics/models/range_of_aqi.dart index 0308d564..dfb48ecb 100644 --- a/lib/pages/analytics/models/range_of_aqi.dart +++ b/lib/pages/analytics/models/range_of_aqi.dart @@ -38,9 +38,9 @@ class RangeOfAqiValue extends Equatable { factory RangeOfAqiValue.fromJson(Map json) { return RangeOfAqiValue( type: json['type'] as String, - min: (json['min'] as num).toDouble(), - average: (json['average'] as num).toDouble(), - max: (json['max'] as num).toDouble(), + min: (json['min'] as num? ?? 0).toDouble(), + average: (json['average'] as num? ?? 0).toDouble(), + max: (json['max'] as num? ?? 0).toDouble(), ); } 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 223c0357..f23abd7b 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 @@ -24,11 +24,13 @@ abstract final class FetchAirQualityDataHelper { }) { final date = context.read().state.monthlyDate; final aqiType = context.read().state.selectedAqiType; - loadAnalyticsDevices( - context, - communityUuid: communityUuid, - spaceUuid: spaceUuid, - ); + if (shouldFetchAnalyticsDevices) { + loadAnalyticsDevices( + context, + communityUuid: communityUuid, + spaceUuid: spaceUuid, + ); + } loadRangeOfAqi( context, spaceUuid: spaceUuid, diff --git a/lib/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart b/lib/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart index 21cb2a9e..e4aa5b6f 100644 --- a/lib/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart +++ b/lib/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart @@ -23,6 +23,7 @@ abstract final class RangeOfAqiChartsHelper { return titlesData.copyWith( bottomTitles: titlesData.bottomTitles.copyWith( sideTitles: titlesData.bottomTitles.sideTitles.copyWith( + reservedSize: 36, getTitlesWidget: (value, meta) => Padding( padding: const EdgeInsetsDirectional.only(top: 20.0), child: Text( @@ -40,6 +41,7 @@ abstract final class RangeOfAqiChartsHelper { reservedSize: 70, interval: 50, maxIncluded: false, + minIncluded: true, getTitlesWidget: (value, meta) { final text = value >= 300 ? '301+' : value.toInt().toString(); return Padding( diff --git a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart index b6d403eb..61179d15 100644 --- a/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart +++ b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_distribution_chart_box.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_legend.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/range_of_aqi_chart_box.dart'; class AirQualityView extends StatelessWidget { @@ -20,6 +21,10 @@ class AirQualityView extends StatelessWidget { child: Column( spacing: 32, children: [ + SizedBox( + height: height * 0.1, + child: const AqiLegend(), + ), SizedBox( height: height * 1.2, child: const AirQualityEndSideWidget(), @@ -40,7 +45,7 @@ class AirQualityView extends StatelessWidget { return SingleChildScrollView( child: Container( padding: _padding, - height: height * 1.1, + height: height * 1.2, child: const Column( children: [ Expanded( @@ -52,8 +57,9 @@ class AirQualityView extends StatelessWidget { child: Column( spacing: 20, children: [ - Expanded(child: RangeOfAqiChartBox()), - Expanded(child: AqiDistributionChartBox()), + Expanded(flex: 2, child: AqiLegend()), + Expanded(flex: 12, child: RangeOfAqiChartBox()), + Expanded(flex: 12, child: AqiDistributionChartBox()), ], ), ), 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 2f3d7ff0..e35a05e7 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 @@ -32,8 +32,13 @@ class AqiDistributionChart extends StatelessWidget { } List _buildBarGroups() { - return List.generate(chartData.length, (index) { - final data = chartData[index]; + final groups = []; + for (var i = 0; i < chartData.length; i++) { + final data = chartData[i]; + final isAllZero = data.data.every((d) => d.percentage == 0); + if (isAllZero) { + continue; + } final stackItems = []; double currentY = 0; var isFirstElement = true; @@ -56,13 +61,15 @@ class AqiDistributionChart extends StatelessWidget { currentY += percentageData.percentage + _rodStackItemsSpacing; isFirstElement = false; } - - return BarChartGroupData( - x: index, - barRods: stackItems, - groupVertically: true, + groups.add( + BarChartGroupData( + x: i, + barRods: stackItems, + groupVertically: true, + ), ); - }); + } + return groups; } BarTouchData _barTouchData(BuildContext context) { @@ -73,6 +80,7 @@ class AqiDistributionChart extends StatelessWidget { color: ColorsManager.semiTransparentBlack, ), tooltipRoundedRadius: 16, + maxContentWidth: 500, tooltipPadding: const EdgeInsets.all(8), getTooltipItem: (group, groupIndex, rod, rodIndex) { final data = chartData[group.x]; @@ -81,10 +89,13 @@ class AqiDistributionChart extends StatelessWidget { final textStyle = context.textTheme.bodySmall?.copyWith( color: ColorsManager.blackColor, - fontSize: 8, + fontSize: 11, ); for (final percentageData in data.data) { + if (percentageData.percentage == 0) { + continue; + } final percentage = percentageData.percentage.toStringAsFixed(1); final type = percentageData.type[0].toUpperCase() + percentageData.type.substring(1).replaceAll('_', ' '); @@ -98,7 +109,7 @@ class AqiDistributionChart extends StatelessWidget { DateFormat('dd/MM/yyyy').format(data.date), context.textTheme.bodyMedium!.copyWith( color: ColorsManager.blackColor, - fontSize: 9, + fontSize: 12, fontWeight: FontWeight.w600, ), textAlign: TextAlign.start, @@ -118,7 +129,6 @@ class AqiDistributionChart extends StatelessWidget { final leftTitles = titlesData.leftTitles.copyWith( sideTitles: titlesData.leftTitles.sideTitles.copyWith( reservedSize: 70, - interval: 20, maxIncluded: false, minIncluded: true, getTitlesWidget: (value, meta) => Padding( @@ -140,7 +150,7 @@ class AqiDistributionChart extends StatelessWidget { final bottomTitles = AxisTitles( sideTitles: SideTitles( - showTitles: true, + showTitles: chartData.isNotEmpty, getTitlesWidget: (value, _) => FittedBox( alignment: AlignmentDirectional.bottomCenter, fit: BoxFit.scaleDown, @@ -148,7 +158,7 @@ class AqiDistributionChart extends StatelessWidget { chartData[value.toInt()].date.day.toString(), style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.lightGreyColor, - fontSize: 8, + fontSize: 12, ), ), ), 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 926d28e1..f7be6ee3 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 @@ -19,7 +19,7 @@ class AqiDistributionChartTitle extends StatelessWidget { children: [ ChartsLoadingWidget(isLoading: isLoading), const Expanded( - flex: 3, + flex: 4, child: FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerStart, @@ -28,23 +28,26 @@ class AqiDistributionChartTitle extends StatelessWidget { ), ), ), - FittedBox( - alignment: AlignmentDirectional.centerEnd, - fit: BoxFit.scaleDown, - child: AqiTypeDropdown( - onChanged: (value) { - if (value != null) { - final bloc = context.read(); - try { - final param = _makeLoadAqiDistributionParam(context, value); - bloc.add(LoadAirQualityDistribution(param)); - } catch (_) { - return; - } finally { - bloc.add(UpdateAqiTypeEvent(value)); + Expanded( + flex: 2, + child: FittedBox( + alignment: AlignmentDirectional.centerEnd, + fit: BoxFit.scaleDown, + child: AqiTypeDropdown( + onChanged: (value) { + if (value != null) { + final bloc = context.read(); + try { + final param = _makeLoadAqiDistributionParam(context, value); + bloc.add(LoadAirQualityDistribution(param)); + } catch (_) { + return; + } finally { + bloc.add(UpdateAqiTypeEvent(value)); + } } - } - }, + }, + ), ), ), ], diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_legend.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_legend.dart new file mode 100644 index 00000000..3a00925d --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_legend.dart @@ -0,0 +1,38 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/helpers/range_of_aqi_charts_helper.dart'; +import 'package:syncrow_web/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class AqiLegend extends StatelessWidget { + const AqiLegend({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsetsDirectional.all(20), + decoration: subSectionContainerDecoration.copyWith( + borderRadius: BorderRadius.circular(20), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + spacing: 16, + children: RangeOfAqiChartsHelper.gradientData.map((e) { + return Flexible( + flex: 4, + child: FittedBox( + fit: BoxFit.fill, + child: ChartInformativeCell( + color: e.$1, + title: FittedBox( + fit: BoxFit.fill, + child: Text(e.$2), + ), + height: null, + ), + ), + ); + }).toList(), + ), + ); + } +} diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart index fa0216a1..00233ad3 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart @@ -47,36 +47,37 @@ class AqiLocationInfoCell extends StatelessWidget { ), ), Align( - alignment: AlignmentDirectional.bottomEnd, - child: Padding( - padding: const EdgeInsetsDirectional.all(10), - child: SizedBox( - height: 40, - width: 120, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.bottomEnd, - child: Text( - value, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.vividBlue.withValues(alpha: 0.7), - fontWeight: FontWeight.w700, - fontSize: 24, + alignment: AlignmentDirectional.bottomCenter, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Expanded( + child: SvgPicture.asset( + svgPath, + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.bottomStart, + ), + ), + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.bottomEnd, + child: Padding( + padding: const EdgeInsetsDirectional.all(10), + child: Text( + value, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.vividBlue.withValues( + alpha: 0.7, + ), + fontWeight: FontWeight.w700, + fontSize: 24, + ), + ), ), ), ), - ), - ), - ), - Align( - alignment: AlignmentDirectional.bottomStart, - child: SizedBox.square( - dimension: MediaQuery.sizeOf(context).width * 0.45, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.bottomStart, - child: SvgPicture.asset(svgPath), - ), + ], ), ), ], diff --git a/lib/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart b/lib/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart index eec31998..f79ecb44 100644 --- a/lib/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart +++ b/lib/pages/analytics/modules/analytics/widgets/chart_informative_cell.dart @@ -7,16 +7,18 @@ class ChartInformativeCell extends StatelessWidget { required this.title, required this.color, this.hasBorder = false, + this.height, }); final Widget title; final Color color; final bool hasBorder; + final double? height; @override Widget build(BuildContext context) { return Container( - height: MediaQuery.sizeOf(context).height * 0.0385, + height: height ?? MediaQuery.sizeOf(context).height * 0.0385, padding: const EdgeInsetsDirectional.symmetric( vertical: 8, horizontal: 12,