From 95eca869c9a0c7d06bc6d58a3bef0c4265d9597f Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 15:12:11 +0300 Subject: [PATCH 01/32] Implemented `AqiSubValueWidget`. --- .../air_quality/views/air_quality_view.dart | 7 +- .../widgets/air_quality_end_side_widget.dart | 41 ++++++ .../widgets/aqi_sub_value_widget.dart | 139 ++++++++++++++++++ 3 files changed, 185 insertions(+), 2 deletions(-) create mode 100644 lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart 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 38f62cd7..f2b485f7 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 @@ -40,7 +40,7 @@ class AirQualityView extends StatelessWidget { spacing: 32, children: [ Expanded( - flex: 2, + flex: 5, child: Column( spacing: 20, children: [ @@ -49,7 +49,10 @@ class AirQualityView extends StatelessWidget { ], ), ), - Expanded(child: AirQualityEndSideWidget()), + Expanded( + flex: 3, + child: AirQualityEndSideWidget(), + ), ], ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index 2d6ace36..8c4bc9c1 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.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/helpers/fetch_energy_management_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart'; @@ -39,6 +40,46 @@ class AirQualityEndSideWidget extends StatelessWidget { fontSize: 12, ), ), + const Divider(), + Container( + decoration: secondarySection.copyWith(boxShadow: const []), + padding: const EdgeInsetsDirectional.all(20), + child: const Column( + spacing: 6, + children: [ + AqiSubValueWidget( + label: 'PM2.5', + value: 19, + unit: 'µg/m³', + ), + AqiSubValueWidget( + label: 'PM10', + value: 42, + unit: 'µg/m³', + ), + AqiSubValueWidget( + label: 'CO2', + value: 610, + unit: 'ppm', + ), + AqiSubValueWidget( + label: 'VOC', + value: 1, + unit: 'mg/m³', + ), + AqiSubValueWidget( + label: 'O3', + value: 55, + unit: 'µg/m³', + ), + AqiSubValueWidget( + label: 'NO2', + value: 18, + unit: 'µg/m³', + ), + ], + ), + ), ], ), ); diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart new file mode 100644 index 00000000..d9843bca --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +final class _AqiRange { + const _AqiRange({ + required this.max, + required this.color, + }); + + final int max; + final Color color; +} + +class AqiSubValueWidget extends StatelessWidget { + final String label; + final int value; + final String unit; + + const AqiSubValueWidget({ + super.key, + required this.label, + required this.value, + required this.unit, + }); + + static const List<_AqiRange> _ranges = [ + _AqiRange(max: 12, color: ColorsManager.green), + _AqiRange(max: 35, color: Color(0xFFFFF176)), + _AqiRange(max: 55, color: Color(0xFFFFD54F)), + _AqiRange(max: 150, color: Color(0xFFE57373)), + _AqiRange(max: 250, color: Color(0xFFBA68C8)), + _AqiRange(max: 500, color: Color(0xFFB39DDB)), + ]; + + int _getActiveSegment(int value) { + for (int i = 0; i < _ranges.length; i++) { + if (value <= _ranges[i].max) return i; + } + return _ranges.length - 1; + } + + @override + Widget build(BuildContext context) { + final activeSegment = _getActiveSegment(value); + return Container( + padding: const EdgeInsetsDirectional.all(10), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + spacing: MediaQuery.sizeOf(context).width * 0.0075, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildLabel(context), + _buildSegmentedBar(activeSegment), + _buildValueAndUnit(context), + ], + ), + ); + } + + Widget _buildValueAndUnit(BuildContext context) { + return Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerEnd, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Text( + value.toString(), + style: context.textTheme.titleMedium?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + Text( + unit, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 10, + ), + ), + ], + ), + ), + ); + } + + Widget _buildSegmentedBar(int activeSegment) { + return Expanded( + flex: 4, + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: 12), + child: Row( + spacing: 4, + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(_ranges.length, (index) { + final isActive = index == activeSegment; + final color = _ranges[index].color.withValues( + alpha: isActive ? 1.0 : 0.25, + ); + return Expanded( + child: Container( + height: 5, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(4), + ), + ), + ); + }), + ), + ), + ); + } + + Widget _buildLabel(BuildContext context) { + return Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerStart, + child: Text( + label, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + ), + ); + } +} From 7726ceecb85a50028f7a816d591505ac5b14c2a3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 15:21:45 +0300 Subject: [PATCH 02/32] made `AqiSubValueWidget` use the correct colors. --- .../modules/air_quality/views/air_quality_view.dart | 2 +- .../air_quality/widgets/aqi_sub_value_widget.dart | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) 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 9cf35128..746a5754 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 @@ -51,7 +51,7 @@ class AirQualityView extends StatelessWidget { ), ), Expanded( - flex: 3, + flex: 2, child: AirQualityEndSideWidget(), ), ], diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart index d9843bca..c4ed3b15 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart @@ -25,12 +25,12 @@ class AqiSubValueWidget extends StatelessWidget { }); static const List<_AqiRange> _ranges = [ - _AqiRange(max: 12, color: ColorsManager.green), - _AqiRange(max: 35, color: Color(0xFFFFF176)), - _AqiRange(max: 55, color: Color(0xFFFFD54F)), - _AqiRange(max: 150, color: Color(0xFFE57373)), - _AqiRange(max: 250, color: Color(0xFFBA68C8)), - _AqiRange(max: 500, color: Color(0xFFB39DDB)), + _AqiRange(max: 12, color: ColorsManager.goodGreen), + _AqiRange(max: 35, color: ColorsManager.poorOrange), + _AqiRange(max: 55, color: ColorsManager.poorOrange), + _AqiRange(max: 150, color: ColorsManager.unhealthyRed), + _AqiRange(max: 250, color: ColorsManager.severePink), + _AqiRange(max: 500, color: ColorsManager.hazardousPurple), ]; int _getActiveSegment(int value) { From 0bf34c66aa1a74c37cf0aef545a93864c6934bf4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 15:45:32 +0300 Subject: [PATCH 03/32] Animated `AqiSubValueWidget`. --- .../widgets/aqi_sub_value_widget.dart | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart index c4ed3b15..b23a32d8 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart @@ -3,27 +3,24 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; final class _AqiRange { - const _AqiRange({ - required this.max, - required this.color, - }); + const _AqiRange({required this.max, required this.color}); final int max; final Color color; } class AqiSubValueWidget extends StatelessWidget { - final String label; - final int value; - final String unit; - const AqiSubValueWidget({ - super.key, required this.label, required this.value, required this.unit, + super.key, }); + final String label; + final int value; + final String unit; + static const List<_AqiRange> _ranges = [ _AqiRange(max: 12, color: ColorsManager.goodGreen), _AqiRange(max: 35, color: ColorsManager.poorOrange), @@ -106,7 +103,9 @@ class AqiSubValueWidget extends StatelessWidget { alpha: isActive ? 1.0 : 0.25, ); return Expanded( - child: Container( + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + curve: Curves.linear, height: 5, decoration: BoxDecoration( color: color, From 34279cfdaed80fe1d136a131d227a0e2e29073b3 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 16:10:49 +0300 Subject: [PATCH 04/32] added `location_pin.svg` icon. --- assets/icons/location_pin.svg | 11 +++++++++++ lib/utils/constants/assets.dart | 1 + 2 files changed, 12 insertions(+) create mode 100644 assets/icons/location_pin.svg diff --git a/assets/icons/location_pin.svg b/assets/icons/location_pin.svg new file mode 100644 index 00000000..e1ae063f --- /dev/null +++ b/assets/icons/location_pin.svg @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 13d51ea5..9c9146a4 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -452,4 +452,5 @@ class Assets { 'assets/icons/refresh_status_icon.svg'; static const String energyConsumedIcon = 'assets/icons/energy_consumed_icon.svg'; + static const String locationPin = 'assets/icons/location_pin.svg'; } From fe716baba7957d730ac3f5d3204558aecd473570 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 16:10:58 +0300 Subject: [PATCH 05/32] created `AqiLocation` widget. --- .../air_quality/widgets/aqi_location.dart | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart new file mode 100644 index 00000000..3ad7e495 --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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 AqiLocation extends StatelessWidget { + const AqiLocation({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: subSectionContainerDecoration.copyWith(boxShadow: const []), + padding: const EdgeInsetsDirectional.all(10), + child: Row( + spacing: 10, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + _buildLocationPin(), + Expanded( + child: Text( + 'Business Bay, Dubai - UAE', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ), + ], + ), + ); + } + + Widget _buildLocationPin() { + return SvgPicture.asset( + Assets.locationPin, + height: 12, + width: 12, + alignment: AlignmentDirectional.centerStart, + colorFilter: const ColorFilter.mode( + ColorsManager.vividBlue, + BlendMode.srcIn, + ), + ); + } +} From 1f444ccfcbf7be175d5d02fbfe2dc8cbe31188e5 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 16:11:13 +0300 Subject: [PATCH 06/32] Created `AqiLocationInfoCell` widget. --- .../widgets/aqi_location_info_cell.dart | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 lib/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart 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 new file mode 100644 index 00000000..b5cc000b --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart @@ -0,0 +1,81 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class AqiLocationInfoCell extends StatelessWidget { + const AqiLocationInfoCell({ + required this.label, + required this.value, + required this.svgPath, + super.key, + }); + + final String label; + final String value; + final String svgPath; + + @override + Widget build(BuildContext context) { + return Expanded( + child: Container( + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(12), + ), + child: Stack( + children: [ + Align( + alignment: AlignmentDirectional.topStart, + child: Padding( + padding: const EdgeInsetsDirectional.all(10), + child: Text( + label, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional.bottomEnd, + child: Padding( + padding: const EdgeInsetsDirectional.all(10).add( + const EdgeInsetsDirectional.only(start: 32), + ), + child: FittedBox( + child: Text( + value, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.vividBlue.withValues(alpha: 0.7), + fontWeight: FontWeight.w700, + fontSize: 40, + ), + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional.bottomStart, + child: SizedBox.square( + dimension: 75, + child: FittedBox( + child: SvgPicture.asset( + svgPath, + colorFilter: ColorFilter.mode( + ColorsManager.textPrimaryColor.withValues(alpha: 0.2), + BlendMode.srcIn, + ), + fit: BoxFit.scaleDown, + ), + ), + ), + ), + ], + ), + ), + ); + } +} From 077c6e99d6e63b8205882f4d6f2bc476784c67e4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 16:55:17 +0300 Subject: [PATCH 07/32] added aqi informative icons. --- assets/icons/aqi_air_quality.svg | 5 +++++ assets/icons/aqi_humidity.svg | 7 +++++++ assets/icons/aqi_temperature.svg | 8 ++++++++ lib/utils/constants/assets.dart | 3 +++ 4 files changed, 23 insertions(+) create mode 100644 assets/icons/aqi_air_quality.svg create mode 100644 assets/icons/aqi_humidity.svg create mode 100644 assets/icons/aqi_temperature.svg diff --git a/assets/icons/aqi_air_quality.svg b/assets/icons/aqi_air_quality.svg new file mode 100644 index 00000000..cd2ed556 --- /dev/null +++ b/assets/icons/aqi_air_quality.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/icons/aqi_humidity.svg b/assets/icons/aqi_humidity.svg new file mode 100644 index 00000000..dd8a1d7e --- /dev/null +++ b/assets/icons/aqi_humidity.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/aqi_temperature.svg b/assets/icons/aqi_temperature.svg new file mode 100644 index 00000000..09ab6d77 --- /dev/null +++ b/assets/icons/aqi_temperature.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 9c9146a4..64ecd0a9 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -453,4 +453,7 @@ class Assets { static const String energyConsumedIcon = 'assets/icons/energy_consumed_icon.svg'; static const String locationPin = 'assets/icons/location_pin.svg'; + static const String aqiTemperature = 'assets/icons/aqi_temperature.svg'; + static const String aqiHumidity = 'assets/icons/aqi_humidity.svg'; + static const String aqiAirQuality = 'assets/icons/aqi_air_quality.svg'; } From aded80fb9ab116dc78218d44318c0c09ee2f3e44 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 16:55:43 +0300 Subject: [PATCH 08/32] modified sizing of `AirQualityView`. --- .../analytics/modules/air_quality/views/air_quality_view.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 746a5754..85028636 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 @@ -33,7 +33,7 @@ class AirQualityView extends StatelessWidget { return SingleChildScrollView( child: Container( padding: _padding, - height: height, + height: height * 0.9, child: const Column( children: [ Expanded( From 42319cc4f972808646e1fe5b5618deb98084b963 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Tue, 27 May 2025 16:56:01 +0300 Subject: [PATCH 09/32] added `unit` property to `AqiType`. --- .../air_quality/widgets/aqi_type_dropdown.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 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 ea85f075..196f02ce 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,16 +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'), - pm10('PM10'), - hcho('HCHO'), - tvoc('TVOC'), - co2('CO2'), - c6h6('C6H6'); + aqi('AQI', ''), + pm25('PM2.5', 'µg/m³'), + pm10('PM10', 'µg/m³'), + hcho('HCHO', 'mg/m³'), + tvoc('TVOC', 'µg/m³'), + co2('CO2', 'ppm'), + c6h6('C6H6', 'µg/m³'); + + const AqiType(this.value, this.unit); final String value; - const AqiType(this.value); + final String unit; } class AqiTypeDropdown extends StatefulWidget { From fc330d6e172678f5724bef3b3e283480257dfca4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 09:32:58 +0300 Subject: [PATCH 10/32] Making good progress towards finalizing the end side bar. --- .../air_quality/views/air_quality_view.dart | 2 +- .../air_quality_end_side_gauge_and_info.dart | 77 +++++++++++ .../air_quality_end_side_live_indicator.dart | 40 ++++++ .../widgets/air_quality_end_side_widget.dart | 128 +++++++++++++----- .../widgets/aqi_location_info_cell.dart | 40 +++--- .../widgets/aqi_sub_value_widget.dart | 30 ++-- 6 files changed, 248 insertions(+), 69 deletions(-) create mode 100644 lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart create mode 100644 lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_live_indicator.dart 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 85028636..b4ae5f1b 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 @@ -33,7 +33,7 @@ class AirQualityView extends StatelessWidget { return SingleChildScrollView( child: Container( padding: _padding, - height: height * 0.9, + height: height * 1.1, child: const Column( children: [ Expanded( diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart new file mode 100644 index 00000000..086eda9e --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -0,0 +1,77 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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'; + +class AirQualityEndSideGaugeAndInfo extends StatelessWidget { + const AirQualityEndSideGaugeAndInfo({super.key}); + + @override + Widget build(BuildContext context) { + return Expanded( + flex: 3, + child: Row( + children: [ + const Expanded(flex: 2, child: Placeholder()), + const Spacer(), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Air Quality:', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + Text( + 'Perfect', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.green, + fontWeight: FontWeight.w400, + fontSize: 30, + ), + ), + DefaultTextStyle( + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.aqiTemperature, + height: 12, + width: 12, + colorFilter: const ColorFilter.mode( + ColorsManager.goodGreen, + BlendMode.srcIn, + ), + ), + const SizedBox(width: 4), + const Text('30C'), + const SizedBox(width: 10), + SvgPicture.asset( + Assets.aqiHumidity, + height: 12, + width: 12, + colorFilter: const ColorFilter.mode( + ColorsManager.textPrimaryColor, + BlendMode.srcIn, + ), + ), + const SizedBox(width: 4), + const Text('30%'), + ], + ), + ), + ], + ), + ], + ), + ); + } +} diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_live_indicator.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_live_indicator.dart new file mode 100644 index 00000000..da8dd86a --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_live_indicator.dart @@ -0,0 +1,40 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class AirQualityEndSideLiveIndicator extends StatelessWidget { + const AirQualityEndSideLiveIndicator({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + 'Entrance', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14, + ), + ), + const Spacer(), + CircleAvatar( + backgroundColor: ColorsManager.green.withValues( + alpha: 0.5, + ), + radius: 2, + ), + const SizedBox(width: 4), + Text( + 'Live', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.green.withValues(alpha: 0.5), + fontWeight: FontWeight.w400, + fontSize: 8, + ), + ), + ], + ); + } +} diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index 0a91fc49..03044e8d 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -1,10 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_live_indicator.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.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_devices/analytics_devices_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.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'; @@ -41,43 +47,93 @@ class AirQualityEndSideWidget extends StatelessWidget { ), ), const Divider(), - Container( - decoration: secondarySection.copyWith(boxShadow: const []), - padding: const EdgeInsetsDirectional.all(20), - child: const Column( - spacing: 6, - children: [ - AqiSubValueWidget( - label: 'PM2.5', - value: 19, - unit: 'µg/m³', + Expanded( + flex: 15, + child: Container( + decoration: secondarySection.copyWith(boxShadow: const []), + padding: const EdgeInsetsDirectional.all(20), + child: Expanded( + child: Column( + spacing: 6, + children: [ + const AirQualityEndSideLiveIndicator(), + const AirQualityEndSideGaugeAndInfo(), + const SizedBox(height: 20), + // The spaces added to the labels are for alignment purposes, because FittedBox is used to align the text. + AqiSubValueWidget( + label: AqiType.pm25.value, + value: 19, + unit: AqiType.pm25.unit, + ), + AqiSubValueWidget( + label: AqiType.pm10.value, + value: 42, + unit: AqiType.pm10.unit, + ), + AqiSubValueWidget( + label: '${AqiType.co2.value} ', + value: 610, + unit: AqiType.co2.unit, + ), + AqiSubValueWidget( + label: AqiType.hcho.value, + value: 1, + unit: AqiType.hcho.unit, + ), + AqiSubValueWidget( + label: AqiType.tvoc.value, + value: 55, + unit: AqiType.tvoc.unit, + ), + AqiSubValueWidget( + label: AqiType.co2.value, + value: 18, + unit: AqiType.co2.unit, + ), + AqiSubValueWidget( + label: AqiType.c6h6.value, + value: 18, + unit: AqiType.c6h6.unit, + ), + ], ), - AqiSubValueWidget( - label: 'PM10', - value: 42, - unit: 'µg/m³', - ), - AqiSubValueWidget( - label: 'CO2', - value: 610, - unit: 'ppm', - ), - AqiSubValueWidget( - label: 'VOC', - value: 1, - unit: 'mg/m³', - ), - AqiSubValueWidget( - label: 'O3', - value: 55, - unit: 'µg/m³', - ), - AqiSubValueWidget( - label: 'NO2', - value: 18, - unit: 'µg/m³', - ), - ], + ), + ), + ), + const SizedBox(height: 20), + Expanded( + flex: 6, + child: Container( + decoration: secondarySection.copyWith(boxShadow: const []), + padding: const EdgeInsetsDirectional.all(20), + child: const Column( + spacing: 8, + children: [ + AqiLocation(), + Expanded( + child: Row( + spacing: 8, + children: [ + AqiLocationInfoCell( + label: 'Temperature', + value: '25°', + svgPath: Assets.aqiTemperature, + ), + AqiLocationInfoCell( + label: 'Humidity', + value: '25%', + svgPath: Assets.aqiHumidity, + ), + AqiLocationInfoCell( + label: 'Air Quality', + value: '120', + svgPath: Assets.aqiAirQuality, + ), + ], + ), + ), + ], + ), ), ), ], 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 b5cc000b..07c27f04 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 @@ -17,6 +17,7 @@ class AqiLocationInfoCell extends StatelessWidget { @override Widget build(BuildContext context) { + final isMediumOrLess = MediaQuery.sizeOf(context).width <= 900; return Expanded( child: Container( decoration: BoxDecoration( @@ -27,14 +28,18 @@ class AqiLocationInfoCell extends StatelessWidget { children: [ Align( alignment: AlignmentDirectional.topStart, - child: Padding( - padding: const EdgeInsetsDirectional.all(10), - child: Text( - label, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 12, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.topStart, + child: Padding( + padding: const EdgeInsetsDirectional.all(10), + child: Text( + label, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), ), ), ), @@ -46,12 +51,14 @@ class AqiLocationInfoCell extends StatelessWidget { const EdgeInsetsDirectional.only(start: 32), ), 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: 40, + fontSize: 24, ), ), ), @@ -60,16 +67,13 @@ class AqiLocationInfoCell extends StatelessWidget { Align( alignment: AlignmentDirectional.bottomStart, child: SizedBox.square( - dimension: 75, + dimension: isMediumOrLess + ? MediaQuery.sizeOf(context).width * 0.15 + : MediaQuery.sizeOf(context).width * 0.035, child: FittedBox( - child: SvgPicture.asset( - svgPath, - colorFilter: ColorFilter.mode( - ColorsManager.textPrimaryColor.withValues(alpha: 0.2), - BlendMode.srcIn, - ), - fit: BoxFit.scaleDown, - ), + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.bottomStart, + child: SvgPicture.asset(svgPath), ), ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart index b23a32d8..6596019a 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart @@ -40,20 +40,22 @@ class AqiSubValueWidget extends StatelessWidget { @override Widget build(BuildContext context) { final activeSegment = _getActiveSegment(value); - return Container( - padding: const EdgeInsetsDirectional.all(10), - decoration: BoxDecoration( - color: ColorsManager.whiteColors, - borderRadius: BorderRadius.circular(12), - ), - child: Row( - spacing: MediaQuery.sizeOf(context).width * 0.0075, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - _buildLabel(context), - _buildSegmentedBar(activeSegment), - _buildValueAndUnit(context), - ], + return Expanded( + child: Container( + padding: const EdgeInsetsDirectional.all(10), + decoration: BoxDecoration( + color: ColorsManager.whiteColors, + borderRadius: BorderRadius.circular(12), + ), + child: Row( + spacing: MediaQuery.sizeOf(context).width * 0.0075, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + _buildLabel(context), + _buildSegmentedBar(activeSegment), + _buildValueAndUnit(context), + ], + ), ), ); } From 595966d30631163cd02e2417b24d14f1d5104fa0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 14:22:35 +0300 Subject: [PATCH 11/32] implemented gauge. --- .../air_quality/views/air_quality_view.dart | 2 +- .../air_quality_end_side_gauge_and_info.dart | 267 ++++++++++++++---- pubspec.yaml | 1 + 3 files changed, 218 insertions(+), 52 deletions(-) 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 b4ae5f1b..c24922cf 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 @@ -51,7 +51,7 @@ class AirQualityView extends StatelessWidget { ), ), Expanded( - flex: 2, + flex: 3, child: AirQualityEndSideWidget(), ), ], diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart index 086eda9e..8e01a99a 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:gauge_indicator/gauge_indicator.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'; @@ -10,65 +11,229 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { @override Widget build(BuildContext context) { return Expanded( - flex: 3, + flex: 2, child: Row( children: [ - const Expanded(flex: 2, child: Placeholder()), + const Expanded( + flex: 3, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Expanded(flex: 1, child: AqiGauge(aqi: 200)), + ], + ), + ), const Spacer(), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'Air Quality:', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 14, - ), - ), - Text( - 'Perfect', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.green, - fontWeight: FontWeight.w400, - fontSize: 30, - ), - ), - DefaultTextStyle( - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.aqiTemperature, - height: 12, - width: 12, - colorFilter: const ColorFilter.mode( - ColorsManager.goodGreen, - BlendMode.srcIn, + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + flex: 2, + child: FittedBox( + alignment: AlignmentDirectional.centerStart, + child: Text( + 'Air Quality:', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 14, ), ), - const SizedBox(width: 4), - const Text('30C'), - const SizedBox(width: 10), - SvgPicture.asset( - Assets.aqiHumidity, - height: 12, - width: 12, - colorFilter: const ColorFilter.mode( - ColorsManager.textPrimaryColor, - BlendMode.srcIn, + ), + ), + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerStart, + child: Text( + 'Perfect', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.green, + fontWeight: FontWeight.w400, + fontSize: 30, ), ), - const SizedBox(width: 4), - const Text('30%'), - ], + ), ), - ), - ], + Expanded( + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerStart, + child: DefaultTextStyle( + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.aqiTemperature, + height: 12, + width: 12, + colorFilter: const ColorFilter.mode( + ColorsManager.goodGreen, + BlendMode.srcIn, + ), + ), + const SizedBox(width: 4), + const Text('30C'), + const SizedBox(width: 10), + SvgPicture.asset( + Assets.aqiHumidity, + height: 12, + width: 12, + colorFilter: const ColorFilter.mode( + ColorsManager.textPrimaryColor, + BlendMode.srcIn, + ), + ), + const SizedBox(width: 4), + const Text('30%'), + ], + ), + ), + ), + ), + ], + ), + ), + ], + ), + ); + } +} + +class AqiGauge extends StatelessWidget { + const AqiGauge({super.key, required this.aqi}); + + final double aqi; + + Color _getPointerColor(double value) { + if (value <= 50) { + return ColorsManager.goodGreen; + } else if (value <= 100) { + return ColorsManager.moderateYellow; + } else if (value <= 150) { + return ColorsManager.poorOrange; + } else { + if (value <= 225) { + final t = (value - 151) / (225 - 151); + return Color.lerp( + ColorsManager.unhealthyRed, + ColorsManager.severePink, + t, + )!; + } else { + final t = (value - 226) / (300 - 226); + return Color.lerp( + ColorsManager.severePink, + ColorsManager.hazardousPurple, + t, + )!; + } + } + } + + @override + Widget build(BuildContext context) { + return AnimatedRadialGauge( + value: aqi, + debug: false, + duration: const Duration(milliseconds: 200), + initialValue: 0, + alignment: Alignment.bottomCenter, + builder: (context, child, value) { + return Align( + alignment: AlignmentDirectional.bottomCenter, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.bottomCenter, + child: Text.rich( + TextSpan( + text: '${value.toInt()}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w700, + fontSize: 30, + ), + children: [ + const TextSpan( + text: 'AQI', + style: TextStyle( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ], + ), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ), + ); + }, + axis: GaugeAxis( + progressBar: const GaugeProgressBar.basic(color: Colors.transparent), + style: const GaugeAxisStyle( + cornerRadius: Radius.circular(16), + thickness: 10, + segmentSpacing: 4, + ), + min: 0, + max: 300, + pointer: GaugePointer.circle( + position: const GaugePointerPosition.surface(), + shadow: const BoxShadow( + color: ColorsManager.transparentColor, + blurRadius: 0, + offset: Offset(0, 0), + ), + radius: MediaQuery.sizeOf(context).width * 0.004, + color: ColorsManager.whiteColors, + border: GaugePointerBorder( + width: 4, + color: _getPointerColor(aqi), + ), + ), + transformer: const GaugeAxisTransformer.colorFadeIn( + background: Colors.transparent, + interval: Interval(0, 0), + ), + segments: [ + const GaugeSegment( + from: 0, + to: 50, + cornerRadius: Radius.circular(16), + color: ColorsManager.goodGreen, + ), + const GaugeSegment( + from: 51, + to: 100, + cornerRadius: Radius.circular(16), + color: ColorsManager.moderateYellow, + ), + const GaugeSegment( + from: 101, + to: 150, + cornerRadius: Radius.circular(16), + color: ColorsManager.poorOrange, + ), + const GaugeSegment( + from: 151, + to: 300, + cornerRadius: Radius.circular(16), + gradient: GaugeAxisGradient( + colorStops: [0.0, 0.5, 1.0], + colors: [ + ColorsManager.unhealthyRed, + ColorsManager.severePink, + ColorsManager.hazardousPurple, + ], + ), ), ], ), diff --git a/pubspec.yaml b/pubspec.yaml index 7decc506..2d09f0bb 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -61,6 +61,7 @@ dependencies: firebase_crashlytics: ^4.3.2 firebase_database: ^11.3.2 bloc: ^9.0.0 + gauge_indicator: ^0.4.3 dev_dependencies: From a23370471ce9328c7892a7d6f0e400e36a7ad530 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 14:39:41 +0300 Subject: [PATCH 12/32] improved sizing of `AqiLocationInfoCell`. --- .../modules/air_quality/widgets/aqi_location_info_cell.dart | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) 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 07c27f04..e6ee71d7 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 @@ -17,7 +17,6 @@ class AqiLocationInfoCell extends StatelessWidget { @override Widget build(BuildContext context) { - final isMediumOrLess = MediaQuery.sizeOf(context).width <= 900; return Expanded( child: Container( decoration: BoxDecoration( @@ -67,9 +66,7 @@ class AqiLocationInfoCell extends StatelessWidget { Align( alignment: AlignmentDirectional.bottomStart, child: SizedBox.square( - dimension: isMediumOrLess - ? MediaQuery.sizeOf(context).width * 0.15 - : MediaQuery.sizeOf(context).width * 0.035, + dimension: MediaQuery.sizeOf(context).width * 0.45, child: FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.bottomStart, From 9a41e0c4f5875cd17e372304bb3c77ebdd751dc9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 14:50:46 +0300 Subject: [PATCH 13/32] moved `ApiGauge` to its own file. --- .../air_quality_end_side_gauge_and_info.dart | 166 ++---------------- .../widgets/air_quality_end_side_widget.dart | 2 +- .../air_quality/widgets/aqi_gauge.dart | 139 +++++++++++++++ 3 files changed, 155 insertions(+), 152 deletions(-) create mode 100644 lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart index 8e01a99a..8693a6d2 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:gauge_indicator/gauge_indicator.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_gauge.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'; @@ -26,12 +26,14 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { ), const Spacer(), Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Expanded( - flex: 2, - child: FittedBox( + child: Padding( + padding: const EdgeInsetsDirectional.only(end: 20), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + const Spacer(), + FittedBox( + fit: BoxFit.contain, alignment: AlignmentDirectional.centerStart, child: Text( 'Air Quality:', @@ -42,10 +44,8 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { ), ), ), - ), - Expanded( - child: FittedBox( - fit: BoxFit.scaleDown, + FittedBox( + fit: BoxFit.contain, alignment: AlignmentDirectional.centerStart, child: Text( 'Perfect', @@ -56,9 +56,8 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { ), ), ), - ), - Expanded( - child: FittedBox( + const Spacer(), + FittedBox( fit: BoxFit.scaleDown, alignment: AlignmentDirectional.centerStart, child: DefaultTextStyle( @@ -79,7 +78,7 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { ), ), const SizedBox(width: 4), - const Text('30C'), + const Text('30°C'), const SizedBox(width: 10), SvgPicture.asset( Assets.aqiHumidity, @@ -91,148 +90,13 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { ), ), const SizedBox(width: 4), - const Text('30%'), + const Text('30°C'), ], ), ), ), - ), - ], - ), - ), - ], - ), - ); - } -} - -class AqiGauge extends StatelessWidget { - const AqiGauge({super.key, required this.aqi}); - - final double aqi; - - Color _getPointerColor(double value) { - if (value <= 50) { - return ColorsManager.goodGreen; - } else if (value <= 100) { - return ColorsManager.moderateYellow; - } else if (value <= 150) { - return ColorsManager.poorOrange; - } else { - if (value <= 225) { - final t = (value - 151) / (225 - 151); - return Color.lerp( - ColorsManager.unhealthyRed, - ColorsManager.severePink, - t, - )!; - } else { - final t = (value - 226) / (300 - 226); - return Color.lerp( - ColorsManager.severePink, - ColorsManager.hazardousPurple, - t, - )!; - } - } - } - - @override - Widget build(BuildContext context) { - return AnimatedRadialGauge( - value: aqi, - debug: false, - duration: const Duration(milliseconds: 200), - initialValue: 0, - alignment: Alignment.bottomCenter, - builder: (context, child, value) { - return Align( - alignment: AlignmentDirectional.bottomCenter, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.bottomCenter, - child: Text.rich( - TextSpan( - text: '${value.toInt()}', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w700, - fontSize: 30, - ), - children: [ - const TextSpan( - text: 'AQI', - style: TextStyle( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - ), ], ), - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.center, - ), - ), - ); - }, - axis: GaugeAxis( - progressBar: const GaugeProgressBar.basic(color: Colors.transparent), - style: const GaugeAxisStyle( - cornerRadius: Radius.circular(16), - thickness: 10, - segmentSpacing: 4, - ), - min: 0, - max: 300, - pointer: GaugePointer.circle( - position: const GaugePointerPosition.surface(), - shadow: const BoxShadow( - color: ColorsManager.transparentColor, - blurRadius: 0, - offset: Offset(0, 0), - ), - radius: MediaQuery.sizeOf(context).width * 0.004, - color: ColorsManager.whiteColors, - border: GaugePointerBorder( - width: 4, - color: _getPointerColor(aqi), - ), - ), - transformer: const GaugeAxisTransformer.colorFadeIn( - background: Colors.transparent, - interval: Interval(0, 0), - ), - segments: [ - const GaugeSegment( - from: 0, - to: 50, - cornerRadius: Radius.circular(16), - color: ColorsManager.goodGreen, - ), - const GaugeSegment( - from: 51, - to: 100, - cornerRadius: Radius.circular(16), - color: ColorsManager.moderateYellow, - ), - const GaugeSegment( - from: 101, - to: 150, - cornerRadius: Radius.circular(16), - color: ColorsManager.poorOrange, - ), - const GaugeSegment( - from: 151, - to: 300, - cornerRadius: Radius.circular(16), - gradient: GaugeAxisGradient( - colorStops: [0.0, 0.5, 1.0], - colors: [ - ColorsManager.unhealthyRed, - ColorsManager.severePink, - ColorsManager.hazardousPurple, - ], ), ), ], diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index 03044e8d..e636d97b 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -102,7 +102,7 @@ class AirQualityEndSideWidget extends StatelessWidget { ), const SizedBox(height: 20), Expanded( - flex: 6, + flex: 4, child: Container( decoration: secondarySection.copyWith(boxShadow: const []), padding: const EdgeInsetsDirectional.all(20), diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart new file mode 100644 index 00000000..d36f3e00 --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart @@ -0,0 +1,139 @@ +import 'package:flutter/material.dart'; +import 'package:gauge_indicator/gauge_indicator.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class AqiGauge extends StatelessWidget { + const AqiGauge({super.key, required this.aqi}); + + final double aqi; + + Color _getPointerColor(double value) { + if (value <= 50) { + return ColorsManager.goodGreen; + } else if (value <= 100) { + return ColorsManager.moderateYellow; + } else if (value <= 150) { + return ColorsManager.poorOrange; + } else { + if (value <= 225) { + final t = (value - 151) / (225 - 151); + return Color.lerp( + ColorsManager.unhealthyRed, + ColorsManager.severePink, + t, + )!; + } else { + final t = (value - 226) / (300 - 226); + return Color.lerp( + ColorsManager.severePink, + ColorsManager.hazardousPurple, + t, + )!; + } + } + } + + @override + Widget build(BuildContext context) { + return AnimatedRadialGauge( + value: aqi, + debug: false, + duration: const Duration(milliseconds: 200), + initialValue: 0, + alignment: Alignment.bottomCenter, + builder: (context, child, value) { + return Align( + alignment: AlignmentDirectional.bottomCenter, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.bottomCenter, + child: Text.rich( + TextSpan( + text: '${value.toInt()}', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w700, + fontSize: 30, + ), + children: [ + const TextSpan( + text: 'AQI', + style: TextStyle( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + ], + ), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.center, + ), + ), + ); + }, + axis: GaugeAxis( + progressBar: const GaugeProgressBar.basic(color: Colors.transparent), + style: const GaugeAxisStyle( + cornerRadius: Radius.circular(16), + thickness: 10, + segmentSpacing: 4, + ), + min: 0, + max: 300, + pointer: GaugePointer.circle( + position: const GaugePointerPosition.surface(), + shadow: const BoxShadow( + color: ColorsManager.transparentColor, + blurRadius: 0, + offset: Offset(0, 0), + ), + radius: MediaQuery.sizeOf(context).width * 0.004, + color: ColorsManager.whiteColors, + border: GaugePointerBorder( + width: 4, + color: _getPointerColor(aqi), + ), + ), + transformer: const GaugeAxisTransformer.colorFadeIn( + background: Colors.transparent, + interval: Interval(0, 0), + ), + segments: [ + const GaugeSegment( + from: 0, + to: 50, + cornerRadius: Radius.circular(16), + color: ColorsManager.goodGreen, + ), + const GaugeSegment( + from: 51, + to: 100, + cornerRadius: Radius.circular(16), + color: ColorsManager.moderateYellow, + ), + const GaugeSegment( + from: 101, + to: 150, + cornerRadius: Radius.circular(16), + color: ColorsManager.poorOrange, + ), + const GaugeSegment( + from: 151, + to: 300, + cornerRadius: Radius.circular(16), + gradient: GaugeAxisGradient( + colorStops: [0.0, 0.5, 1.0], + colors: [ + ColorsManager.unhealthyRed, + ColorsManager.severePink, + ColorsManager.hazardousPurple, + ], + ), + ), + ], + ), + ); + } +} From 16dc066440a466ff9fbecec51f98aeb201f32fe6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 14:56:14 +0300 Subject: [PATCH 14/32] removed unnecessary comment. --- .../air_quality/widgets/air_quality_end_side_widget.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index e636d97b..23c81350 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -59,7 +59,6 @@ class AirQualityEndSideWidget extends StatelessWidget { const AirQualityEndSideLiveIndicator(), const AirQualityEndSideGaugeAndInfo(), const SizedBox(height: 20), - // The spaces added to the labels are for alignment purposes, because FittedBox is used to align the text. AqiSubValueWidget( label: AqiType.pm25.value, value: 19, @@ -71,7 +70,7 @@ class AirQualityEndSideWidget extends StatelessWidget { unit: AqiType.pm10.unit, ), AqiSubValueWidget( - label: '${AqiType.co2.value} ', + label: AqiType.co2.value, value: 610, unit: AqiType.co2.unit, ), From 7c69c7ddbd74ea8e6ab22c8c1735be10be08e1b6 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 15:19:26 +0300 Subject: [PATCH 15/32] fixed responsiveness of end side bar. --- .../air_quality/widgets/air_quality_end_side_widget.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index 23c81350..1c56b6d1 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -145,9 +145,9 @@ class AirQualityEndSideWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Expanded( - flex: 3, child: FittedBox( alignment: AlignmentDirectional.centerStart, + fit: BoxFit.scaleDown, child: SelectableText( 'AQI Sensor', style: context.textTheme.headlineSmall?.copyWith( @@ -160,9 +160,10 @@ class AirQualityEndSideWidget extends StatelessWidget { ), const Spacer(), Expanded( - flex: 4, + flex: 2, child: FittedBox( alignment: AlignmentDirectional.centerEnd, + fit: BoxFit.scaleDown, child: AnalyticsDeviceDropdown( onChanged: (value) { context.read().add( From a87b11d084435840e62a283bea5a1412546d6f29 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 15:25:17 +0300 Subject: [PATCH 16/32] adjusted the size of `AqiGauge` and removed unnecessary code. --- .../widgets/air_quality_end_side_gauge_and_info.dart | 2 +- .../analytics/modules/air_quality/widgets/aqi_gauge.dart | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart index 8693a6d2..3f6006c3 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -15,7 +15,7 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { child: Row( children: [ const Expanded( - flex: 3, + flex: 2, child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart index d36f3e00..5d284afb 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart @@ -77,22 +77,17 @@ class AqiGauge extends StatelessWidget { progressBar: const GaugeProgressBar.basic(color: Colors.transparent), style: const GaugeAxisStyle( cornerRadius: Radius.circular(16), - thickness: 10, + thickness: 14, segmentSpacing: 4, ), min: 0, max: 300, pointer: GaugePointer.circle( position: const GaugePointerPosition.surface(), - shadow: const BoxShadow( - color: ColorsManager.transparentColor, - blurRadius: 0, - offset: Offset(0, 0), - ), radius: MediaQuery.sizeOf(context).width * 0.004, color: ColorsManager.whiteColors, border: GaugePointerBorder( - width: 4, + width: 6, color: _getPointerColor(aqi), ), ), From 5d3ef95cb7d13a74d78d914f5a02ca6c2ad55e92 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 15:30:12 +0300 Subject: [PATCH 17/32] Refactor `AqiGauge` to use constants for range values, to allow for ease of change, and readability. --- .../air_quality/widgets/aqi_gauge.dart | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart index 5d284afb..a287e8cd 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart @@ -7,24 +7,33 @@ class AqiGauge extends StatelessWidget { const AqiGauge({super.key, required this.aqi}); final double aqi; + static const _minRange = 0.0; + static const _goodRange = 50.0; + static const _moderateRange = 100.0; + static const _poorRange = 150.0; + static const _unhealthyToSevereRange = 225.0; + static const _maxRange = 300.0; Color _getPointerColor(double value) { - if (value <= 50) { + if (value <= _goodRange) { return ColorsManager.goodGreen; - } else if (value <= 100) { + } else if (value <= _moderateRange) { return ColorsManager.moderateYellow; - } else if (value <= 150) { + } else if (value <= _poorRange) { return ColorsManager.poorOrange; } else { - if (value <= 225) { - final t = (value - 151) / (225 - 151); + const unhealthyStart = _poorRange + 1; + if (value <= _unhealthyToSevereRange) { + final t = + (value - unhealthyStart) / (_unhealthyToSevereRange - unhealthyStart); return Color.lerp( ColorsManager.unhealthyRed, ColorsManager.severePink, t, )!; } else { - final t = (value - 226) / (300 - 226); + const severeStart = _unhealthyToSevereRange + 1; + final t = (value - severeStart) / (_maxRange - severeStart); return Color.lerp( ColorsManager.severePink, ColorsManager.hazardousPurple, @@ -81,7 +90,7 @@ class AqiGauge extends StatelessWidget { segmentSpacing: 4, ), min: 0, - max: 300, + max: _maxRange, pointer: GaugePointer.circle( position: const GaugePointerPosition.surface(), radius: MediaQuery.sizeOf(context).width * 0.004, @@ -97,26 +106,26 @@ class AqiGauge extends StatelessWidget { ), segments: [ const GaugeSegment( - from: 0, - to: 50, + from: _minRange, + to: _goodRange, cornerRadius: Radius.circular(16), color: ColorsManager.goodGreen, ), const GaugeSegment( - from: 51, - to: 100, + from: _goodRange + 1, + to: _moderateRange, cornerRadius: Radius.circular(16), color: ColorsManager.moderateYellow, ), const GaugeSegment( - from: 101, - to: 150, + from: _moderateRange + 1, + to: _poorRange, cornerRadius: Radius.circular(16), color: ColorsManager.poorOrange, ), const GaugeSegment( - from: 151, - to: 300, + from: _poorRange + 1, + to: _maxRange, cornerRadius: Radius.circular(16), gradient: GaugeAxisGradient( colorStops: [0.0, 0.5, 1.0], From 5b91ceb6394e9dda1ad27ce47116d90e363b5174 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 15:33:28 +0300 Subject: [PATCH 18/32] enhanced animation of `AqiGague` --- .../analytics/modules/air_quality/widgets/aqi_gauge.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart index a287e8cd..70cd3be5 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart @@ -48,7 +48,8 @@ class AqiGauge extends StatelessWidget { return AnimatedRadialGauge( value: aqi, debug: false, - duration: const Duration(milliseconds: 200), + duration: const Duration(milliseconds: 500), + curve: Curves.easeInOut, initialValue: 0, alignment: Alignment.bottomCenter, builder: (context, child, value) { @@ -89,7 +90,7 @@ class AqiGauge extends StatelessWidget { thickness: 14, segmentSpacing: 4, ), - min: 0, + min: _minRange, max: _maxRange, pointer: GaugePointer.circle( position: const GaugePointerPosition.surface(), From fd186a00fd9c9aa2d205d529f9d284ed2cea2287 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 15:41:18 +0300 Subject: [PATCH 19/32] add shadow to pointer to match the design. --- .../analytics/modules/air_quality/widgets/aqi_gauge.dart | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart index 70cd3be5..0061f7e8 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart @@ -60,7 +60,7 @@ class AqiGauge extends StatelessWidget { alignment: AlignmentDirectional.bottomCenter, child: Text.rich( TextSpan( - text: '${value.toInt()}', + text: value.toStringAsFixed(0), style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w700, @@ -100,9 +100,14 @@ class AqiGauge extends StatelessWidget { width: 6, color: _getPointerColor(aqi), ), + shadow: const BoxShadow( + color: ColorsManager.blackColor, + blurRadius: 6, + offset: Offset(0, 2), + ), ), transformer: const GaugeAxisTransformer.colorFadeIn( - background: Colors.transparent, + background: ColorsManager.transparentColor, interval: Interval(0, 0), ), segments: [ From 79c5fe1651b9bd5526f8b35465dcec470b51164a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 16:13:23 +0300 Subject: [PATCH 20/32] add icons for side bar info (humidity and tempreture). --- assets/icons/humidity.svg | 7 +++++++ assets/icons/thermometer.svg | 8 ++++++++ .../widgets/air_quality_end_side_gauge_and_info.dart | 6 +++--- .../air_quality/widgets/air_quality_end_side_widget.dart | 2 +- lib/utils/constants/assets.dart | 2 ++ 5 files changed, 21 insertions(+), 4 deletions(-) create mode 100644 assets/icons/humidity.svg create mode 100644 assets/icons/thermometer.svg diff --git a/assets/icons/humidity.svg b/assets/icons/humidity.svg new file mode 100644 index 00000000..585ac31f --- /dev/null +++ b/assets/icons/humidity.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/assets/icons/thermometer.svg b/assets/icons/thermometer.svg new file mode 100644 index 00000000..94aa72eb --- /dev/null +++ b/assets/icons/thermometer.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart index 3f6006c3..1e669b13 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -69,11 +69,11 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { child: Row( children: [ SvgPicture.asset( - Assets.aqiTemperature, + Assets.temperatureAqiSidebar, height: 12, width: 12, colorFilter: const ColorFilter.mode( - ColorsManager.goodGreen, + ColorsManager.textPrimaryColor, BlendMode.srcIn, ), ), @@ -81,7 +81,7 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { const Text('30°C'), const SizedBox(width: 10), SvgPicture.asset( - Assets.aqiHumidity, + Assets.humidityAqiSidebar, height: 12, width: 12, colorFilter: const ColorFilter.mode( diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index 1c56b6d1..89dd50c9 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -101,7 +101,7 @@ class AirQualityEndSideWidget extends StatelessWidget { ), const SizedBox(height: 20), Expanded( - flex: 4, + flex: 6, child: Container( decoration: secondarySection.copyWith(boxShadow: const []), padding: const EdgeInsetsDirectional.all(20), diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart index 64ecd0a9..6dcf5f83 100644 --- a/lib/utils/constants/assets.dart +++ b/lib/utils/constants/assets.dart @@ -456,4 +456,6 @@ class Assets { static const String aqiTemperature = 'assets/icons/aqi_temperature.svg'; static const String aqiHumidity = 'assets/icons/aqi_humidity.svg'; static const String aqiAirQuality = 'assets/icons/aqi_air_quality.svg'; + static const String temperatureAqiSidebar = 'assets/icons/thermometer.svg'; + static const String humidityAqiSidebar = 'assets/icons/humidity.svg'; } From cc5f107ccb00bcc510d186ae49269b20a34dc770 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 28 May 2025 16:37:34 +0300 Subject: [PATCH 21/32] Extracted `AqiHumidityAndTemperature` into its own widget and file. --- .../air_quality/views/air_quality_view.dart | 7 +-- .../air_quality_end_side_gauge_and_info.dart | 46 ++--------------- .../widgets/aqi_humidity_and_temperature.dart | 51 +++++++++++++++++++ 3 files changed, 56 insertions(+), 48 deletions(-) create mode 100644 lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart 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 c24922cf..a7f1893f 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 @@ -41,7 +41,7 @@ class AirQualityView extends StatelessWidget { spacing: 32, children: [ Expanded( - flex: 5, + flex: 10, child: Column( spacing: 20, children: [ @@ -50,10 +50,7 @@ class AirQualityView extends StatelessWidget { ], ), ), - Expanded( - flex: 3, - child: AirQualityEndSideWidget(), - ), + Expanded(flex: 5, child: AirQualityEndSideWidget()), ], ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart index 1e669b13..06a33c26 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -1,8 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.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'; class AirQualityEndSideGaugeAndInfo extends StatelessWidget { @@ -19,9 +18,7 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, - children: [ - Expanded(flex: 1, child: AqiGauge(aqi: 200)), - ], + children: [Expanded(child: AqiGauge(aqi: 200))], ), ), const Spacer(), @@ -57,44 +54,7 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { ), ), const Spacer(), - FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.centerStart, - child: DefaultTextStyle( - style: context.textTheme.bodySmall!.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - child: Row( - children: [ - SvgPicture.asset( - Assets.temperatureAqiSidebar, - height: 12, - width: 12, - colorFilter: const ColorFilter.mode( - ColorsManager.textPrimaryColor, - BlendMode.srcIn, - ), - ), - const SizedBox(width: 4), - const Text('30°C'), - const SizedBox(width: 10), - SvgPicture.asset( - Assets.humidityAqiSidebar, - height: 12, - width: 12, - colorFilter: const ColorFilter.mode( - ColorsManager.textPrimaryColor, - BlendMode.srcIn, - ), - ), - const SizedBox(width: 4), - const Text('30°C'), - ], - ), - ), - ), + const AqiHumidityAndTemperature(), ], ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart new file mode 100644 index 00000000..e8a3f7b4 --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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'; + +class AqiHumidityAndTemperature extends StatelessWidget { + const AqiHumidityAndTemperature({super.key}); + + static const iconSize = 12.0; + static const colorFilter = ColorFilter.mode( + ColorsManager.textPrimaryColor, + BlendMode.srcIn, + ); + + @override + Widget build(BuildContext context) { + return FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerStart, + child: DefaultTextStyle( + style: context.textTheme.bodySmall!.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + child: Row( + children: [ + SvgPicture.asset( + Assets.temperatureAqiSidebar, + height: iconSize, + width: iconSize, + colorFilter: colorFilter, + ), + const SizedBox(width: 4), + const Text('30°C'), + const SizedBox(width: 10), + SvgPicture.asset( + Assets.humidityAqiSidebar, + height: iconSize, + width: iconSize, + colorFilter: colorFilter, + ), + const SizedBox(width: 4), + const Text('30°C'), + ], + ), + ), + ); + } +} From a75e6a89a98eab2390c3a3d3aef573e8e760e1e7 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 09:24:29 +0300 Subject: [PATCH 22/32] Enhanced responsiveness of `AqiLocationInfoCell`. --- .../air_quality/views/air_quality_view.dart | 2 +- .../widgets/air_quality_end_side_widget.dart | 4 +- .../widgets/aqi_location_info_cell.dart | 51 ++++++++++--------- 3 files changed, 31 insertions(+), 26 deletions(-) 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 a7f1893f..17ecbc22 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 @@ -50,7 +50,7 @@ class AirQualityView extends StatelessWidget { ], ), ), - Expanded(flex: 5, child: AirQualityEndSideWidget()), + Expanded(flex: 6, child: AirQualityEndSideWidget()), ], ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index 89dd50c9..30dc9b89 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -115,7 +115,7 @@ class AirQualityEndSideWidget extends StatelessWidget { children: [ AqiLocationInfoCell( label: 'Temperature', - value: '25°', + value: ' 25°', svgPath: Assets.aqiTemperature, ), AqiLocationInfoCell( @@ -125,7 +125,7 @@ class AirQualityEndSideWidget extends StatelessWidget { ), AqiLocationInfoCell( label: 'Air Quality', - value: '120', + value: ' 120', svgPath: Assets.aqiAirQuality, ), ], 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 e6ee71d7..fa0216a1 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 @@ -27,17 +27,20 @@ class AqiLocationInfoCell extends StatelessWidget { children: [ Align( alignment: AlignmentDirectional.topStart, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.topStart, - child: Padding( - padding: const EdgeInsetsDirectional.all(10), - child: Text( - label, - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 12, + child: Padding( + padding: const EdgeInsetsDirectional.all(10), + child: SizedBox( + height: 24, + child: FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.topStart, + child: Text( + label, + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), ), ), ), @@ -46,18 +49,20 @@ class AqiLocationInfoCell extends StatelessWidget { Align( alignment: AlignmentDirectional.bottomEnd, child: Padding( - padding: const EdgeInsetsDirectional.all(10).add( - const EdgeInsetsDirectional.only(start: 32), - ), - 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, + 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, + ), ), ), ), From 3d133581ffcf52384f6de7074bc5c956311917a1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 09:59:27 +0300 Subject: [PATCH 23/32] Implemented and used a reusable widget for analytics sidebars headers. --- .../widgets/air_quality_end_side_widget.dart | 69 +------------- .../widgets/analytics_device_dropdown.dart | 26 +++--- .../power_clamp_energy_data_widget.dart | 80 +++-------------- .../widgets/occupancy_end_side_bar.dart | 67 +------------- .../widgets/analytics_sidebar_header.dart | 90 +++++++++++++++++++ 5 files changed, 122 insertions(+), 210 deletions(-) create mode 100644 lib/pages/analytics/widgets/analytics_sidebar_header.dart diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index 30dc9b89..808c3ff8 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -1,17 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_live_indicator.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.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_devices/analytics_devices_bloc.dart'; -import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; -import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/pages/analytics/widgets/analytics_sidebar_header.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 AirQualityEndSideWidget extends StatelessWidget { @@ -27,26 +22,7 @@ class AirQualityEndSideWidget extends StatelessWidget { child: 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( - context.watch().state.selectedDevice?.uuid ?? - 'N/A', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - ), - const Divider(), + const AnalyticsSidebarHeader(title: 'AQI Sensor'), Expanded( flex: 15, child: Container( @@ -139,45 +115,4 @@ class AirQualityEndSideWidget extends StatelessWidget { ), ); } - - Widget _buildHeader(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - child: FittedBox( - alignment: AlignmentDirectional.centerStart, - fit: BoxFit.scaleDown, - child: SelectableText( - 'AQI Sensor', - style: context.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w700, - color: ColorsManager.vividBlue.withValues(alpha: 0.6), - fontSize: 18, - ), - ), - ), - ), - const Spacer(), - Expanded( - flex: 2, - child: FittedBox( - alignment: AlignmentDirectional.centerEnd, - fit: BoxFit.scaleDown, - child: AnalyticsDeviceDropdown( - onChanged: (value) { - context.read().add( - SelectAnalyticsDeviceEvent(value), - ); - FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( - context, - deviceUuid: value.uuid, - ); - }, - ), - ), - ), - ], - ); - } } diff --git a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart index 65157aa4..f7b33309 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart @@ -6,9 +6,14 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AnalyticsDeviceDropdown extends StatelessWidget { - const AnalyticsDeviceDropdown({required this.onChanged, super.key}); + const AnalyticsDeviceDropdown({ + required this.onChanged, + this.showSpaceUuid = false, + super.key, + }); final ValueChanged onChanged; + final bool showSpaceUuid; @override Widget build(BuildContext context) { @@ -72,17 +77,18 @@ class AnalyticsDeviceDropdown extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text(e.name), - if (spaceUuid != null) - FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.centerStart, - child: Text( - spaceUuid, - style: _getTextStyle(context)?.copyWith( - fontSize: 10, + if (showSpaceUuid) + if (spaceUuid != null) + FittedBox( + fit: BoxFit.scaleDown, + alignment: AlignmentDirectional.centerStart, + child: Text( + spaceUuid, + style: _getTextStyle(context)?.copyWith( + fontSize: 10, + ), ), ), - ), ], ), ); diff --git a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart index e8f802cd..4d04a36b 100644 --- a/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart +++ b/lib/pages/analytics/modules/energy_management/widgets/power_clamp_energy_data_widget.dart @@ -2,19 +2,16 @@ 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/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/power_clamp_info/power_clamp_info_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; -import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.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_status_widget.dart'; import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/power_clamp_phases_data_widget.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_error_widget.dart'; +import 'package:syncrow_web/pages/analytics/widgets/analytics_sidebar_header.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 PowerClampEnergyDataWidget extends StatelessWidget { @@ -42,26 +39,18 @@ class PowerClampEnergyDataWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ AnalyticsErrorWidget(state.errorMessage), - _buildHeader(context), - Text( - 'Device ID:', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), + AnalyticsSidebarHeader( + title: 'Smart Power Clamp', + showSpaceUuid: true, + onChanged: (device) { + FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases( + context, + powerClampUuid: device.uuid, + selectedDate: + context.read().state.monthlyDate, + ); + }, ), - const SizedBox(height: 6), - SelectableText( - context.watch().state.selectedDevice?.uuid ?? - 'N/A', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - ), - const Divider(), Expanded( flex: 2, child: PowerClampEnergyStatusWidget( @@ -111,51 +100,6 @@ class PowerClampEnergyDataWidget extends StatelessWidget { ); } - Widget _buildHeader(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - flex: 3, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.centerStart, - child: SelectableText( - 'Smart Power Clamp', - style: context.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w700, - color: ColorsManager.vividBlue.withValues(alpha: 0.6), - fontSize: 18, - ), - ), - ), - ), - const Spacer(), - Expanded( - flex: 2, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.centerEnd, - child: AnalyticsDeviceDropdown( - onChanged: (value) { - FetchEnergyManagementDataHelper.loadEnergyConsumptionByPhases( - context, - powerClampUuid: value.uuid, - selectedDate: - context.read().state.monthlyDate, - ); - FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( - context, - deviceUuid: value.uuid, - ); - }, - ), - ), - ), - ], - ); - } - String _valueFromCode(String code, List points) { return points .firstWhere((e) => e.code == code, orElse: () => DataPoint(value: '--')) diff --git a/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart b/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart index 9f096789..b3f162fa 100644 --- a/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart +++ b/lib/pages/analytics/modules/occupancy/widgets/occupancy_end_side_bar.dart @@ -1,15 +1,11 @@ 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/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'; -import 'package:syncrow_web/pages/analytics/modules/energy_management/helpers/fetch_energy_management_data_helper.dart'; -import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_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/widgets/analytics_sidebar_header.dart'; import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.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 OccupancyEndSideBar extends StatelessWidget { @@ -27,28 +23,7 @@ class OccupancyEndSideBar extends StatelessWidget { child: 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( - context.watch().state.selectedDevice?.uuid ?? - 'N/A', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.blackColor, - fontWeight: FontWeight.w400, - fontSize: 12, - ), - ), - const SizedBox(height: 10), - const Divider(height: 1, color: ColorsManager.greyColor), - const SizedBox(height: 50), + const AnalyticsSidebarHeader(title: 'Presnce Sensor'), SizedBox( height: MediaQuery.sizeOf(context).height * 0.2, child: PowerClampEnergyStatusWidget( @@ -101,42 +76,4 @@ class OccupancyEndSideBar extends StatelessWidget { .toString(); return value == 'null' ? defaultValue ?? '--' : value; } - - Widget _buildHeader(BuildContext context) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Expanded( - flex: 3, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.centerStart, - child: SelectableText( - 'Presnce Sensor', - style: context.textTheme.headlineSmall?.copyWith( - fontWeight: FontWeight.w700, - color: ColorsManager.vividBlue.withValues(alpha: 0.6), - fontSize: 18, - ), - ), - ), - ), - const Spacer(), - Expanded( - flex: 2, - child: FittedBox( - fit: BoxFit.scaleDown, - alignment: AlignmentDirectional.centerEnd, - child: AnalyticsDeviceDropdown( - onChanged: (value) => - FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( - context, - deviceUuid: value.uuid, - ), - ), - ), - ), - ], - ); - } } diff --git a/lib/pages/analytics/widgets/analytics_sidebar_header.dart b/lib/pages/analytics/widgets/analytics_sidebar_header.dart new file mode 100644 index 00000000..5e454ea4 --- /dev/null +++ b/lib/pages/analytics/widgets/analytics_sidebar_header.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/models/analytics_device.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/helpers/fetch_energy_management_data_helper.dart'; +import 'package:syncrow_web/pages/analytics/modules/energy_management/widgets/analytics_device_dropdown.dart'; +import 'package:syncrow_web/utils/color_manager.dart'; +import 'package:syncrow_web/utils/extension/build_context_x.dart'; + +class AnalyticsSidebarHeader extends StatelessWidget { + const AnalyticsSidebarHeader({ + required this.title, + this.showSpaceUuid = false, + this.onChanged, + super.key, + }); + + final String title; + final bool showSpaceUuid; + final void Function(AnalyticsDevice device)? onChanged; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Expanded( + flex: 2, + child: FittedBox( + alignment: AlignmentDirectional.centerStart, + fit: BoxFit.scaleDown, + child: SelectableText( + title, + style: context.textTheme.headlineSmall?.copyWith( + fontWeight: FontWeight.w700, + color: ColorsManager.vividBlue.withValues(alpha: 0.6), + fontSize: 18, + ), + ), + ), + ), + const Spacer(), + Expanded( + flex: 2, + child: FittedBox( + alignment: AlignmentDirectional.centerEnd, + fit: BoxFit.scaleDown, + child: AnalyticsDeviceDropdown( + onChanged: (value) { + context.read().add( + SelectAnalyticsDeviceEvent(value), + ); + FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges( + context, + deviceUuid: value.uuid, + ); + onChanged?.call(value); + }, + ), + ), + ), + ], + ), + Text( + 'Device ID:', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.textPrimaryColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + const SizedBox(height: 6), + SelectableText( + context.watch().state.selectedDevice?.uuid ?? 'N/A', + style: context.textTheme.bodySmall?.copyWith( + color: ColorsManager.blackColor, + fontWeight: FontWeight.w400, + fontSize: 12, + ), + ), + const SizedBox(height: 10), + const Divider(height: 1, color: ColorsManager.greyColor), + const SizedBox(height: 24), + ], + ); + } +} From 94b4aa7c46037f14a8379f7987277d0dd3426bc1 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 11:25:27 +0300 Subject: [PATCH 24/32] Extracted big widgets into smaller ones, and integrated aqi device info with `RealtimeChangesBloc`. --- .../air_quality_end_side_gauge_and_info.dart | 14 ++- .../widgets/air_quality_end_side_widget.dart | 104 ++---------------- .../air_quality/widgets/aqi_device_info.dart | 100 +++++++++++++++++ .../widgets/aqi_humidity_and_temperature.dart | 13 ++- .../widgets/aqi_location_info.dart | 45 ++++++++ .../widgets/aqi_sub_value_widget.dart | 36 ++++-- 6 files changed, 200 insertions(+), 112 deletions(-) create mode 100644 lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart create mode 100644 lib/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart index 06a33c26..8722ec68 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -5,7 +5,14 @@ import 'package:syncrow_web/utils/color_manager.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AirQualityEndSideGaugeAndInfo extends StatelessWidget { - const AirQualityEndSideGaugeAndInfo({super.key}); + const AirQualityEndSideGaugeAndInfo({ + super.key, + required this.temperature, + required this.humidity, + }); + + final int temperature; + final int humidity; @override Widget build(BuildContext context) { @@ -54,7 +61,10 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { ), ), const Spacer(), - const AqiHumidityAndTemperature(), + AqiHumidityAndTemperature( + temperature: temperature, + humidity: humidity, + ), ], ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart index 808c3ff8..6e182e18 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_widget.dart @@ -1,12 +1,7 @@ import 'package:flutter/material.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_live_indicator.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart'; -import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart'; import 'package:syncrow_web/pages/analytics/widgets/analytics_sidebar_header.dart'; -import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/style.dart'; class AirQualityEndSideWidget extends StatelessWidget { @@ -19,98 +14,13 @@ class AirQualityEndSideWidget extends StatelessWidget { borderRadius: BorderRadius.circular(30), ), padding: const EdgeInsetsDirectional.all(32), - child: Column( + child: const Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const AnalyticsSidebarHeader(title: 'AQI Sensor'), - Expanded( - flex: 15, - child: Container( - decoration: secondarySection.copyWith(boxShadow: const []), - padding: const EdgeInsetsDirectional.all(20), - child: Expanded( - child: Column( - spacing: 6, - children: [ - const AirQualityEndSideLiveIndicator(), - const AirQualityEndSideGaugeAndInfo(), - const SizedBox(height: 20), - AqiSubValueWidget( - label: AqiType.pm25.value, - value: 19, - unit: AqiType.pm25.unit, - ), - AqiSubValueWidget( - label: AqiType.pm10.value, - value: 42, - unit: AqiType.pm10.unit, - ), - AqiSubValueWidget( - label: AqiType.co2.value, - value: 610, - unit: AqiType.co2.unit, - ), - AqiSubValueWidget( - label: AqiType.hcho.value, - value: 1, - unit: AqiType.hcho.unit, - ), - AqiSubValueWidget( - label: AqiType.tvoc.value, - value: 55, - unit: AqiType.tvoc.unit, - ), - AqiSubValueWidget( - label: AqiType.co2.value, - value: 18, - unit: AqiType.co2.unit, - ), - AqiSubValueWidget( - label: AqiType.c6h6.value, - value: 18, - unit: AqiType.c6h6.unit, - ), - ], - ), - ), - ), - ), - const SizedBox(height: 20), - Expanded( - flex: 6, - child: Container( - decoration: secondarySection.copyWith(boxShadow: const []), - padding: const EdgeInsetsDirectional.all(20), - child: const Column( - spacing: 8, - children: [ - AqiLocation(), - Expanded( - child: Row( - spacing: 8, - children: [ - AqiLocationInfoCell( - label: 'Temperature', - value: ' 25°', - svgPath: Assets.aqiTemperature, - ), - AqiLocationInfoCell( - label: 'Humidity', - value: '25%', - svgPath: Assets.aqiHumidity, - ), - AqiLocationInfoCell( - label: 'Air Quality', - value: ' 120', - svgPath: Assets.aqiAirQuality, - ), - ], - ), - ), - ], - ), - ), - ), + AnalyticsSidebarHeader(title: 'AQI Sensor'), + Expanded(flex: 15, child: AqiDeviceInfo()), + SizedBox(height: 20), + Expanded(flex: 6, child: AqiLocationInfo()), ], ), ); diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart new file mode 100644 index 00000000..451350ac --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart @@ -0,0 +1,100 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/air_quality_end_side_live_indicator.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart'; +import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; +import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class AqiDeviceInfo extends StatelessWidget { + const AqiDeviceInfo({super.key}); + + double _getValueForStatus( + List deviceStatusList, + String code, { + double defaultValue = 0, + }) { + try { + final foundStatus = deviceStatusList.firstWhere((e) => e.code == code); + return double.parse(foundStatus.value.toString()); + } catch (_) { + return defaultValue; + } + } + + @override + Widget build(BuildContext context) { + return BlocBuilder( + builder: (context, state) { + final status = state.deviceStatusList; + final humidityValue = _getValueForStatus(status, 'humidity_value'); + final tempValue = _getValueForStatus(status, 'temp_current'); + final pm25Value = _getValueForStatus(status, 'pm25_value'); + final pm10Value = _getValueForStatus(status, 'pm10'); + final co2Value = _getValueForStatus(status, 'co2_value'); + final ch2oValue = _getValueForStatus(status, 'ch2o_value'); + final tvocValue = _getValueForStatus(status, 'tvoc_value'); + + return Container( + decoration: secondarySection.copyWith(boxShadow: const []), + padding: const EdgeInsetsDirectional.all(20), + child: Expanded( + child: Column( + spacing: 6, + children: [ + const AirQualityEndSideLiveIndicator(), + AirQualityEndSideGaugeAndInfo( + temperature: humidityValue.toInt(), + humidity: tempValue.toInt(), + ), + const SizedBox(height: 20), + AqiSubValueWidget( + range: (0, 999), + label: AqiType.pm25.value, + value: pm25Value < 100 + ? double.parse(pm25Value.toStringAsFixed(1).padLeft(4, '0')) + : pm25Value, + unit: AqiType.pm25.unit, + ), + AqiSubValueWidget( + range: (0, 999), + label: AqiType.pm10.value, + value: pm10Value < 100 + ? double.parse(pm10Value.toStringAsFixed(1).padLeft(4, '0')) + : pm10Value, + unit: AqiType.pm10.unit, + ), + AqiSubValueWidget( + range: (0, 5), + label: AqiType.hcho.value, + value: double.parse(ch2oValue.toStringAsFixed(2)), + unit: AqiType.hcho.unit, + ), + AqiSubValueWidget( + range: (0, 9.99), + label: AqiType.tvoc.value, + value: tvocValue, + unit: AqiType.tvoc.unit, + ), + AqiSubValueWidget( + range: (0, 5000), + label: AqiType.co2.value, + value: co2Value, + unit: AqiType.co2.unit, + ), + AqiSubValueWidget( + range: (0, 100), + label: AqiType.c6h6.value, + value: 18, + unit: AqiType.c6h6.unit, + ), + ], + ), + ), + ); + }, + ); + } +} diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart index e8a3f7b4..6bea1db9 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart @@ -5,7 +5,13 @@ import 'package:syncrow_web/utils/constants/assets.dart'; import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AqiHumidityAndTemperature extends StatelessWidget { - const AqiHumidityAndTemperature({super.key}); + const AqiHumidityAndTemperature({ + super.key, + required this.temperature, + required this.humidity, + }); + final int temperature; + final int humidity; static const iconSize = 12.0; static const colorFilter = ColorFilter.mode( @@ -13,6 +19,7 @@ class AqiHumidityAndTemperature extends StatelessWidget { BlendMode.srcIn, ); + @override Widget build(BuildContext context) { return FittedBox( @@ -33,7 +40,7 @@ class AqiHumidityAndTemperature extends StatelessWidget { colorFilter: colorFilter, ), const SizedBox(width: 4), - const Text('30°C'), + Text('$temperature°C'), const SizedBox(width: 10), SvgPicture.asset( Assets.humidityAqiSidebar, @@ -42,7 +49,7 @@ class AqiHumidityAndTemperature extends StatelessWidget { colorFilter: colorFilter, ), const SizedBox(width: 4), - const Text('30°C'), + Text('$humidity%'), ], ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart new file mode 100644 index 00000000..f8e087b8 --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart'; +import 'package:syncrow_web/utils/constants/assets.dart'; +import 'package:syncrow_web/utils/style.dart'; + +class AqiLocationInfo extends StatelessWidget { + const AqiLocationInfo({super.key}); + + @override + Widget build(BuildContext context) { + return Container( + decoration: secondarySection.copyWith(boxShadow: const []), + padding: const EdgeInsetsDirectional.all(20), + child: const Column( + spacing: 8, + children: [ + AqiLocation(), + Expanded( + child: Row( + spacing: 8, + children: [ + AqiLocationInfoCell( + label: 'Temperature', + value: ' 25°', + svgPath: Assets.aqiTemperature, + ), + AqiLocationInfoCell( + label: 'Humidity', + value: '25%', + svgPath: Assets.aqiHumidity, + ), + AqiLocationInfoCell( + label: 'Air Quality', + value: ' 120', + svgPath: Assets.aqiAirQuality, + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart index 6596019a..214a4d61 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart @@ -5,7 +5,7 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; final class _AqiRange { const _AqiRange({required this.max, required this.color}); - final int max; + final double max; final Color color; } @@ -14,12 +14,35 @@ class AqiSubValueWidget extends StatelessWidget { required this.label, required this.value, required this.unit, + required this.range, super.key, }); + static List<_AqiRange> _getRangesForValue((double min, double max) range) { + final (double min, double max) = range; + final rangeSize = (max - min) / 6; + return [ + _AqiRange(max: range.$1 + rangeSize, color: ColorsManager.goodGreen), + _AqiRange(max: range.$1 + (rangeSize * 2), color: ColorsManager.poorOrange), + _AqiRange(max: range.$1 + (rangeSize * 3), color: ColorsManager.poorOrange), + _AqiRange(max: range.$1 + (rangeSize * 4), color: ColorsManager.unhealthyRed), + _AqiRange(max: range.$1 + (rangeSize * 5), color: ColorsManager.severePink), + _AqiRange(max: range.$2, color: ColorsManager.hazardousPurple), + ]; + } + + int _getActiveSegmentByRange(double value, (double min, double max) range) { + final ranges = _getRangesForValue(range); + for (int i = 0; i < ranges.length; i++) { + if (value <= ranges[i].max) return i; + } + return ranges.length - 1; + } + final String label; - final int value; + final double value; final String unit; + final (double min, double max) range; static const List<_AqiRange> _ranges = [ _AqiRange(max: 12, color: ColorsManager.goodGreen), @@ -30,16 +53,9 @@ class AqiSubValueWidget extends StatelessWidget { _AqiRange(max: 500, color: ColorsManager.hazardousPurple), ]; - int _getActiveSegment(int value) { - for (int i = 0; i < _ranges.length; i++) { - if (value <= _ranges[i].max) return i; - } - return _ranges.length - 1; - } - @override Widget build(BuildContext context) { - final activeSegment = _getActiveSegment(value); + final activeSegment = _getActiveSegmentByRange(value, range); return Expanded( child: Container( padding: const EdgeInsetsDirectional.all(10), From 43cb985e74c3057b93bc639ccb7d76b22271f616 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 13:05:49 +0300 Subject: [PATCH 25/32] finished integrating realtime data. --- .../air_quality/views/air_quality_view.dart | 21 ++++- .../air_quality/widgets/aqi_device_info.dart | 76 ++++++++++++------- .../widgets/aqi_sub_value_widget.dart | 36 ++++----- .../widgets/aqi_type_dropdown.dart | 3 +- 4 files changed, 87 insertions(+), 49 deletions(-) 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 17ecbc22..3a236a58 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,12 +1,27 @@ import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.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/range_of_aqi_chart_box.dart'; +import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; -class AirQualityView extends StatelessWidget { +class AirQualityView extends StatefulWidget { const AirQualityView({super.key}); static const _padding = EdgeInsetsDirectional.all(32); + @override + State createState() => _AirQualityViewState(); +} + +class _AirQualityViewState extends State { + @override + void initState() { + context.read().add( + const RealtimeDeviceChangesStarted('078e4ed4-8f85-47b8-8c78-5a766549135d'), + ); + super.initState(); + } + @override Widget build(BuildContext context) { return LayoutBuilder( @@ -15,7 +30,7 @@ class AirQualityView extends StatelessWidget { final height = MediaQuery.sizeOf(context).height; if (isMediumOrLess) { return SingleChildScrollView( - padding: _padding, + padding: AirQualityView._padding, child: Column( spacing: 32, children: [ @@ -32,7 +47,7 @@ class AirQualityView extends StatelessWidget { return SingleChildScrollView( child: Container( - padding: _padding, + padding: AirQualityView._padding, height: height * 1.1, child: const Column( children: [ diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart index 451350ac..d9a874bb 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart @@ -11,16 +11,19 @@ import 'package:syncrow_web/utils/style.dart'; class AqiDeviceInfo extends StatelessWidget { const AqiDeviceInfo({super.key}); - double _getValueForStatus( + String _getValueForStatus( List deviceStatusList, String code, { double defaultValue = 0, + String Function(int value)? formatter, }) { try { final foundStatus = deviceStatusList.firstWhere((e) => e.code == code); - return double.parse(foundStatus.value.toString()); - } catch (_) { - return defaultValue; + final value = foundStatus.value.toString(); + final intValue = int.parse(value); + return formatter != null ? formatter(intValue) : intValue.toString(); + } catch (e) { + return defaultValue.toString(); } } @@ -29,13 +32,42 @@ class AqiDeviceInfo extends StatelessWidget { return BlocBuilder( builder: (context, state) { final status = state.deviceStatusList; - final humidityValue = _getValueForStatus(status, 'humidity_value'); - final tempValue = _getValueForStatus(status, 'temp_current'); - final pm25Value = _getValueForStatus(status, 'pm25_value'); - final pm10Value = _getValueForStatus(status, 'pm10'); - final co2Value = _getValueForStatus(status, 'co2_value'); - final ch2oValue = _getValueForStatus(status, 'ch2o_value'); - final tvocValue = _getValueForStatus(status, 'tvoc_value'); + final humidityValue = _getValueForStatus( + status, + 'humidity_value', + formatter: (value) => value.toStringAsFixed(0), + ); + + final tempValue = _getValueForStatus( + status, + 'temp_current', + formatter: (value) => value.toStringAsFixed(0), + ); + final pm25Value = _getValueForStatus( + status, + 'pm25_value', + formatter: (value) => value.toString().padLeft(3, '0'), + ); + final pm10Value = _getValueForStatus( + status, + 'pm10', + formatter: (value) => value.toString().padLeft(3, '0'), + ); + final co2Value = _getValueForStatus( + status, + 'co2_value', + formatter: (value) => value.toString().padLeft(4, '0'), + ); + final ch2oValue = _getValueForStatus( + status, + 'ch2o_value', + formatter: (value) => (value / 100).toStringAsFixed(2), + ); + final tvocValue = _getValueForStatus( + status, + 'tvoc_value', + formatter: (value) => (value / 100).toStringAsFixed(2), + ); return Container( decoration: secondarySection.copyWith(boxShadow: const []), @@ -46,34 +78,30 @@ class AqiDeviceInfo extends StatelessWidget { children: [ const AirQualityEndSideLiveIndicator(), AirQualityEndSideGaugeAndInfo( - temperature: humidityValue.toInt(), - humidity: tempValue.toInt(), + temperature: int.parse(tempValue), + humidity: int.parse(humidityValue), ), const SizedBox(height: 20), AqiSubValueWidget( range: (0, 999), label: AqiType.pm25.value, - value: pm25Value < 100 - ? double.parse(pm25Value.toStringAsFixed(1).padLeft(4, '0')) - : pm25Value, + value: pm25Value, unit: AqiType.pm25.unit, ), AqiSubValueWidget( range: (0, 999), label: AqiType.pm10.value, - value: pm10Value < 100 - ? double.parse(pm10Value.toStringAsFixed(1).padLeft(4, '0')) - : pm10Value, + value: pm10Value, unit: AqiType.pm10.unit, ), AqiSubValueWidget( range: (0, 5), label: AqiType.hcho.value, - value: double.parse(ch2oValue.toStringAsFixed(2)), + value: ch2oValue, unit: AqiType.hcho.unit, ), AqiSubValueWidget( - range: (0, 9.99), + range: (0, 999), label: AqiType.tvoc.value, value: tvocValue, unit: AqiType.tvoc.unit, @@ -84,12 +112,6 @@ class AqiDeviceInfo extends StatelessWidget { value: co2Value, unit: AqiType.co2.unit, ), - AqiSubValueWidget( - range: (0, 100), - label: AqiType.c6h6.value, - value: 18, - unit: AqiType.c6h6.unit, - ), ], ), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart index 214a4d61..29a597a4 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart @@ -17,6 +17,23 @@ class AqiSubValueWidget extends StatelessWidget { required this.range, super.key, }); + + final String label; + final String value; + final String unit; + final (double min, double max) range; + + double get _parsedValue => double.parse(value); + + static const List<_AqiRange> _ranges = [ + _AqiRange(max: 12, color: ColorsManager.goodGreen), + _AqiRange(max: 35, color: ColorsManager.poorOrange), + _AqiRange(max: 55, color: ColorsManager.poorOrange), + _AqiRange(max: 150, color: ColorsManager.unhealthyRed), + _AqiRange(max: 250, color: ColorsManager.severePink), + _AqiRange(max: 500, color: ColorsManager.hazardousPurple), + ]; + static List<_AqiRange> _getRangesForValue((double min, double max) range) { final (double min, double max) = range; final rangeSize = (max - min) / 6; @@ -38,24 +55,9 @@ class AqiSubValueWidget extends StatelessWidget { return ranges.length - 1; } - - final String label; - final double value; - final String unit; - final (double min, double max) range; - - static const List<_AqiRange> _ranges = [ - _AqiRange(max: 12, color: ColorsManager.goodGreen), - _AqiRange(max: 35, color: ColorsManager.poorOrange), - _AqiRange(max: 55, color: ColorsManager.poorOrange), - _AqiRange(max: 150, color: ColorsManager.unhealthyRed), - _AqiRange(max: 250, color: ColorsManager.severePink), - _AqiRange(max: 500, color: ColorsManager.hazardousPurple), - ]; - @override Widget build(BuildContext context) { - final activeSegment = _getActiveSegmentByRange(value, range); + final activeSegment = _getActiveSegmentByRange(_parsedValue, range); return Expanded( child: Container( padding: const EdgeInsetsDirectional.all(10), @@ -86,7 +88,7 @@ class AqiSubValueWidget extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( - value.toString(), + value, style: context.textTheme.titleMedium?.copyWith( color: ColorsManager.blackColor, fontWeight: FontWeight.w400, 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 196f02ce..c725d1fa 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 @@ -8,8 +8,7 @@ enum AqiType { pm10('PM10', 'µg/m³'), hcho('HCHO', 'mg/m³'), tvoc('TVOC', 'µg/m³'), - co2('CO2', 'ppm'), - c6h6('C6H6', 'µg/m³'); + co2('CO2', 'ppm'); const AqiType(this.value, this.unit); From 36ddebb5ae963ba295ce1e2adbbbff1d39d047f8 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 13:28:44 +0300 Subject: [PATCH 26/32] Implemented new gauge design. --- .../air_quality_end_side_gauge_and_info.dart | 45 +++------ .../air_quality/widgets/aqi_device_info.dart | 9 +- .../air_quality/widgets/aqi_gauge.dart | 97 +++++++------------ .../widgets/aqi_humidity_and_temperature.dart | 41 ++++---- 4 files changed, 78 insertions(+), 114 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart index 8722ec68..fb0cac9e 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -1,18 +1,25 @@ import 'package:flutter/material.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart'; -import 'package:syncrow_web/utils/color_manager.dart'; -import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AirQualityEndSideGaugeAndInfo extends StatelessWidget { const AirQualityEndSideGaugeAndInfo({ super.key, required this.temperature, required this.humidity, + required this.aqiLevel, }); final int temperature; final int humidity; + final String aqiLevel; + + double get aqi => switch (aqiLevel) { + 'level_1' => 25.0, + 'level_2' => 75.0, + 'level_3' => 125.0, + _ => 0.0, + }; @override Widget build(BuildContext context) { @@ -20,12 +27,12 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { flex: 2, child: Row( children: [ - const Expanded( + Expanded( flex: 2, child: Row( crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.start, - children: [Expanded(child: AqiGauge(aqi: 200))], + children: [Expanded(child: AqiGauge(aqi: aqi))], ), ), const Spacer(), @@ -33,34 +40,10 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { child: Padding( padding: const EdgeInsetsDirectional.only(end: 20), child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, + // crossAxisAlignment: CrossAxisAlignment.stretch, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, children: [ - const Spacer(), - FittedBox( - fit: BoxFit.contain, - alignment: AlignmentDirectional.centerStart, - child: Text( - 'Air Quality:', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w400, - fontSize: 14, - ), - ), - ), - FittedBox( - fit: BoxFit.contain, - alignment: AlignmentDirectional.centerStart, - child: Text( - 'Perfect', - style: context.textTheme.bodySmall?.copyWith( - color: ColorsManager.green, - fontWeight: FontWeight.w400, - fontSize: 30, - ), - ), - ), - const Spacer(), AqiHumidityAndTemperature( temperature: temperature, humidity: humidity, diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart index d9a874bb..f3773c29 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart @@ -41,7 +41,7 @@ class AqiDeviceInfo extends StatelessWidget { final tempValue = _getValueForStatus( status, 'temp_current', - formatter: (value) => value.toStringAsFixed(0), + formatter: (value) => (value / 10).toStringAsFixed(0), ); final pm25Value = _getValueForStatus( status, @@ -78,6 +78,13 @@ class AqiDeviceInfo extends StatelessWidget { children: [ const AirQualityEndSideLiveIndicator(), AirQualityEndSideGaugeAndInfo( + aqiLevel: status + .firstWhere( + (e) => e.code == 'air_quality_index', + orElse: () => Status(code: 'air_quality_index', value: ''), + ) + .value + .toString(), temperature: int.parse(tempValue), humidity: int.parse(humidityValue), ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart index 0061f7e8..a94d8cd4 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart @@ -10,41 +10,32 @@ class AqiGauge extends StatelessWidget { static const _minRange = 0.0; static const _goodRange = 50.0; static const _moderateRange = 100.0; - static const _poorRange = 150.0; - static const _unhealthyToSevereRange = 225.0; - static const _maxRange = 300.0; + static const _maxRange = 150.0; - Color _getPointerColor(double value) { - if (value <= _goodRange) { - return ColorsManager.goodGreen; - } else if (value <= _moderateRange) { - return ColorsManager.moderateYellow; - } else if (value <= _poorRange) { - return ColorsManager.poorOrange; - } else { - const unhealthyStart = _poorRange + 1; - if (value <= _unhealthyToSevereRange) { - final t = - (value - unhealthyStart) / (_unhealthyToSevereRange - unhealthyStart); - return Color.lerp( - ColorsManager.unhealthyRed, - ColorsManager.severePink, - t, - )!; - } else { - const severeStart = _unhealthyToSevereRange + 1; - final t = (value - severeStart) / (_maxRange - severeStart); - return Color.lerp( - ColorsManager.severePink, - ColorsManager.hazardousPurple, - t, - )!; - } - } + String _getStatusText(double value) { + return switch (value) { + <= _goodRange => 'Good', + <= _moderateRange => 'Moderate', + _ => 'Poor', + }; } + Color _darkenColor(Color color) => Color.lerp( + color, + Colors.black.withValues(alpha: 0.8), + 0.4, + )!; + + Color _getStatusColor(double value) => switch (value) { + <= _goodRange => ColorsManager.goodGreen, + <= _moderateRange => ColorsManager.moderateYellow, + _ => ColorsManager.poorOrange, + }; + @override Widget build(BuildContext context) { + final status = _getStatusText(aqi); + final statusColor = _getStatusColor(aqi); return AnimatedRadialGauge( value: aqi, debug: false, @@ -60,24 +51,23 @@ class AqiGauge extends StatelessWidget { alignment: AlignmentDirectional.bottomCenter, child: Text.rich( TextSpan( - text: value.toStringAsFixed(0), + text: 'Air Quality\n', style: context.textTheme.bodySmall?.copyWith( color: ColorsManager.textPrimaryColor, - fontWeight: FontWeight.w700, - fontSize: 30, + fontWeight: FontWeight.w400, + fontSize: 12, ), children: [ - const TextSpan( - text: 'AQI', - style: TextStyle( - color: ColorsManager.textPrimaryColor, + TextSpan( + text: status, + style: context.textTheme.bodySmall?.copyWith( + color: _darkenColor(statusColor), fontWeight: FontWeight.w400, - fontSize: 12, + fontSize: 30, ), ), ], ), - overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, ), ), @@ -98,7 +88,7 @@ class AqiGauge extends StatelessWidget { color: ColorsManager.whiteColors, border: GaugePointerBorder( width: 6, - color: _getPointerColor(aqi), + color: statusColor, ), shadow: const BoxShadow( color: ColorsManager.blackColor, @@ -106,41 +96,24 @@ class AqiGauge extends StatelessWidget { offset: Offset(0, 2), ), ), - transformer: const GaugeAxisTransformer.colorFadeIn( - background: ColorsManager.transparentColor, - interval: Interval(0, 0), - ), - segments: [ - const GaugeSegment( + segments: const [ + GaugeSegment( from: _minRange, to: _goodRange, cornerRadius: Radius.circular(16), color: ColorsManager.goodGreen, ), - const GaugeSegment( + GaugeSegment( from: _goodRange + 1, to: _moderateRange, cornerRadius: Radius.circular(16), color: ColorsManager.moderateYellow, ), - const GaugeSegment( + GaugeSegment( from: _moderateRange + 1, - to: _poorRange, - cornerRadius: Radius.circular(16), - color: ColorsManager.poorOrange, - ), - const GaugeSegment( - from: _poorRange + 1, to: _maxRange, cornerRadius: Radius.circular(16), - gradient: GaugeAxisGradient( - colorStops: [0.0, 0.5, 1.0], - colors: [ - ColorsManager.unhealthyRed, - ColorsManager.severePink, - ColorsManager.hazardousPurple, - ], - ), + color: ColorsManager.poorOrange, ), ], ), diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart index 6bea1db9..0d6f4ebf 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart @@ -19,7 +19,6 @@ class AqiHumidityAndTemperature extends StatelessWidget { BlendMode.srcIn, ); - @override Widget build(BuildContext context) { return FittedBox( @@ -29,30 +28,32 @@ class AqiHumidityAndTemperature extends StatelessWidget { style: context.textTheme.bodySmall!.copyWith( color: ColorsManager.textPrimaryColor, fontWeight: FontWeight.w400, - fontSize: 12, + fontSize: 16, ), - child: Row( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - SvgPicture.asset( - Assets.temperatureAqiSidebar, - height: iconSize, - width: iconSize, - colorFilter: colorFilter, - ), - const SizedBox(width: 4), - Text('$temperature°C'), - const SizedBox(width: 10), - SvgPicture.asset( - Assets.humidityAqiSidebar, - height: iconSize, - width: iconSize, - colorFilter: colorFilter, - ), - const SizedBox(width: 4), - Text('$humidity%'), + _buildIconAndValue(Assets.temperatureAqiSidebar, '$temperature°C'), + const SizedBox(height: 10), + _buildIconAndValue(Assets.humidityAqiSidebar, '$humidity%'), ], ), ), ); } + + Widget _buildIconAndValue(String icon, String value) { + return Row( + children: [ + SvgPicture.asset( + icon, + height: iconSize, + width: iconSize, + colorFilter: colorFilter, + ), + const SizedBox(width: 4), + Text(value), + ], + ); + } } From 7bd0c061d4d277e02dfab3154032751e9613ae20 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 13:29:52 +0300 Subject: [PATCH 27/32] enhanced design of `AqiLocation`. --- .../analytics/modules/air_quality/widgets/aqi_location.dart | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart index 3ad7e495..3f1d1f09 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart @@ -11,7 +11,10 @@ class AqiLocation extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - decoration: subSectionContainerDecoration.copyWith(boxShadow: const []), + decoration: subSectionContainerDecoration.copyWith( + boxShadow: const [], + borderRadius: BorderRadius.circular(10), + ), padding: const EdgeInsetsDirectional.all(10), child: Row( spacing: 10, From bc289a0ddfe1092fa648a12a2457197e4e436c03 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 14:45:03 +0300 Subject: [PATCH 28/32] removed testing code. --- .../air_quality/views/air_quality_view.dart | 21 +++---------------- 1 file changed, 3 insertions(+), 18 deletions(-) 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 3a236a58..17ecbc22 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,27 +1,12 @@ import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.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/range_of_aqi_chart_box.dart'; -import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/realtime_device_changes/realtime_device_changes_bloc.dart'; -class AirQualityView extends StatefulWidget { +class AirQualityView extends StatelessWidget { const AirQualityView({super.key}); static const _padding = EdgeInsetsDirectional.all(32); - @override - State createState() => _AirQualityViewState(); -} - -class _AirQualityViewState extends State { - @override - void initState() { - context.read().add( - const RealtimeDeviceChangesStarted('078e4ed4-8f85-47b8-8c78-5a766549135d'), - ); - super.initState(); - } - @override Widget build(BuildContext context) { return LayoutBuilder( @@ -30,7 +15,7 @@ class _AirQualityViewState extends State { final height = MediaQuery.sizeOf(context).height; if (isMediumOrLess) { return SingleChildScrollView( - padding: AirQualityView._padding, + padding: _padding, child: Column( spacing: 32, children: [ @@ -47,7 +32,7 @@ class _AirQualityViewState extends State { return SingleChildScrollView( child: Container( - padding: AirQualityView._padding, + padding: _padding, height: height * 1.1, child: const Column( children: [ From b95f4063d9d540ee3671cf8d0155e9cc3f3a917b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 14:54:53 +0300 Subject: [PATCH 29/32] removed unused widget. --- .../air_quality_end_side_gauge_and_info.dart | 27 +++++++------------ 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart index fb0cac9e..93904604 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart @@ -14,13 +14,6 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { final int humidity; final String aqiLevel; - double get aqi => switch (aqiLevel) { - 'level_1' => 25.0, - 'level_2' => 75.0, - 'level_3' => 125.0, - _ => 0.0, - }; - @override Widget build(BuildContext context) { return Expanded( @@ -39,16 +32,9 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { Expanded( child: Padding( padding: const EdgeInsetsDirectional.only(end: 20), - child: Column( - // crossAxisAlignment: CrossAxisAlignment.stretch, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - AqiHumidityAndTemperature( - temperature: temperature, - humidity: humidity, - ), - ], + child: AqiHumidityAndTemperature( + temperature: temperature, + humidity: humidity, ), ), ), @@ -56,4 +42,11 @@ class AirQualityEndSideGaugeAndInfo extends StatelessWidget { ), ); } + + double get aqi => switch (aqiLevel) { + 'level_1' => 25.0, + 'level_2' => 75.0, + 'level_3' => 125.0, + _ => 0.0, + }; } From 3d4c17214c7863e0459856dbaca918ce0a1542ac Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 14:56:56 +0300 Subject: [PATCH 30/32] Refactored `AqiGauge` to consolidate status text and color logic into a single method, improving code readability and maintainability. --- .../air_quality/widgets/aqi_gauge.dart | 37 ++++++++----------- 1 file changed, 15 insertions(+), 22 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart index a94d8cd4..fc7f923a 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart @@ -7,35 +7,15 @@ class AqiGauge extends StatelessWidget { const AqiGauge({super.key, required this.aqi}); final double aqi; + static const _minRange = 0.0; static const _goodRange = 50.0; static const _moderateRange = 100.0; static const _maxRange = 150.0; - String _getStatusText(double value) { - return switch (value) { - <= _goodRange => 'Good', - <= _moderateRange => 'Moderate', - _ => 'Poor', - }; - } - - Color _darkenColor(Color color) => Color.lerp( - color, - Colors.black.withValues(alpha: 0.8), - 0.4, - )!; - - Color _getStatusColor(double value) => switch (value) { - <= _goodRange => ColorsManager.goodGreen, - <= _moderateRange => ColorsManager.moderateYellow, - _ => ColorsManager.poorOrange, - }; - @override Widget build(BuildContext context) { - final status = _getStatusText(aqi); - final statusColor = _getStatusColor(aqi); + final (status, statusColor) = _getStatusData(aqi); return AnimatedRadialGauge( value: aqi, debug: false, @@ -119,4 +99,17 @@ class AqiGauge extends StatelessWidget { ), ); } + + (String status, Color color) _getStatusData(double value) { + return switch (value) { + <= _goodRange => ('Good', ColorsManager.goodGreen), + <= _moderateRange => ('Moderate', ColorsManager.moderateYellow), + _ => ('Poor', ColorsManager.poorOrange), + }; + } + + Color _darkenColor(Color color) { + final black = Colors.black.withValues(alpha: 0.8); + return Color.lerp(color, black, 0.4)!; + } } From 5636fbe6c9bc2fd048ae8613f71a2e819d61b413 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 14:57:26 +0300 Subject: [PATCH 31/32] sorted constructor dependencies. --- .../air_quality/widgets/aqi_humidity_and_temperature.dart | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart index 0d6f4ebf..7726ff32 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart @@ -6,10 +6,11 @@ import 'package:syncrow_web/utils/extension/build_context_x.dart'; class AqiHumidityAndTemperature extends StatelessWidget { const AqiHumidityAndTemperature({ - super.key, required this.temperature, required this.humidity, + super.key, }); + final int temperature; final int humidity; From 283a0dd536c820aee80c0bf1ff6fc94df7fec999 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 14:59:03 +0300 Subject: [PATCH 32/32] Updated `AqiSubValueWidget` to use minimum value for range calculations, improving accuracy in AQI range display. --- .../air_quality/widgets/aqi_sub_value_widget.dart | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart index 29a597a4..5a8e6e6c 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart @@ -38,12 +38,12 @@ class AqiSubValueWidget extends StatelessWidget { final (double min, double max) = range; final rangeSize = (max - min) / 6; return [ - _AqiRange(max: range.$1 + rangeSize, color: ColorsManager.goodGreen), - _AqiRange(max: range.$1 + (rangeSize * 2), color: ColorsManager.poorOrange), - _AqiRange(max: range.$1 + (rangeSize * 3), color: ColorsManager.poorOrange), - _AqiRange(max: range.$1 + (rangeSize * 4), color: ColorsManager.unhealthyRed), - _AqiRange(max: range.$1 + (rangeSize * 5), color: ColorsManager.severePink), - _AqiRange(max: range.$2, color: ColorsManager.hazardousPurple), + _AqiRange(max: min + rangeSize, color: ColorsManager.goodGreen), + _AqiRange(max: min + (rangeSize * 2), color: ColorsManager.poorOrange), + _AqiRange(max: min + (rangeSize * 3), color: ColorsManager.poorOrange), + _AqiRange(max: min + (rangeSize * 4), color: ColorsManager.unhealthyRed), + _AqiRange(max: min + (rangeSize * 5), color: ColorsManager.severePink), + _AqiRange(max: min, color: ColorsManager.hazardousPurple), ]; }