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/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/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/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/views/air_quality_view.dart b/lib/pages/analytics/modules/air_quality/views/air_quality_view.dart
index 03df3ba9..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
@@ -33,7 +33,7 @@ class AirQualityView extends StatelessWidget {
return SingleChildScrollView(
child: Container(
padding: _padding,
- height: height,
+ height: height * 1.1,
child: const Column(
children: [
Expanded(
@@ -41,7 +41,7 @@ class AirQualityView extends StatelessWidget {
spacing: 32,
children: [
Expanded(
- flex: 5,
+ flex: 10,
child: Column(
spacing: 20,
children: [
@@ -50,7 +50,7 @@ class AirQualityView extends StatelessWidget {
],
),
),
- Expanded(flex: 2, child: AirQualityEndSideWidget()),
+ Expanded(flex: 6, 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
new file mode 100644
index 00000000..93904604
--- /dev/null
+++ b/lib/pages/analytics/modules/air_quality/widgets/air_quality_end_side_gauge_and_info.dart
@@ -0,0 +1,52 @@
+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';
+
+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;
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ flex: 2,
+ child: Row(
+ children: [
+ Expanded(
+ flex: 2,
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisAlignment: MainAxisAlignment.start,
+ children: [Expanded(child: AqiGauge(aqi: aqi))],
+ ),
+ ),
+ const Spacer(),
+ Expanded(
+ child: Padding(
+ padding: const EdgeInsetsDirectional.only(end: 20),
+ child: AqiHumidityAndTemperature(
+ temperature: temperature,
+ humidity: humidity,
+ ),
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+
+ double get aqi => switch (aqiLevel) {
+ 'level_1' => 25.0,
+ 'level_2' => 75.0,
+ 'level_3' => 125.0,
+ _ => 0.0,
+ };
+}
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 106685c1..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,10 +1,7 @@
import 'package:flutter/material.dart';
-import 'package:flutter_bloc/flutter_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/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';
+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/style.dart';
class AirQualityEndSideWidget extends StatelessWidget {
@@ -17,70 +14,15 @@ class AirQualityEndSideWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(30),
),
padding: const EdgeInsetsDirectional.all(32),
- child: Column(
+ child: const 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,
- ),
- ),
+ AnalyticsSidebarHeader(title: 'AQI Sensor'),
+ Expanded(flex: 15, child: AqiDeviceInfo()),
+ SizedBox(height: 20),
+ Expanded(flex: 6, child: AqiLocationInfo()),
],
),
);
}
-
- Widget _buildHeader(BuildContext context) {
- return Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Expanded(
- flex: 3,
- child: FittedBox(
- alignment: AlignmentDirectional.centerStart,
- 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: 4,
- child: FittedBox(
- alignment: AlignmentDirectional.centerEnd,
- child: AnalyticsDeviceDropdown(
- onChanged: (value) {
- context.read().add(
- SelectAnalyticsDeviceEvent(value),
- );
- FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
- context,
- deviceUuid: value.uuid,
- );
- },
- ),
- ),
- ),
- ],
- );
- }
}
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..f3773c29
--- /dev/null
+++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart
@@ -0,0 +1,129 @@
+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});
+
+ String _getValueForStatus(
+ List deviceStatusList,
+ String code, {
+ double defaultValue = 0,
+ String Function(int value)? formatter,
+ }) {
+ try {
+ final foundStatus = deviceStatusList.firstWhere((e) => e.code == code);
+ final value = foundStatus.value.toString();
+ final intValue = int.parse(value);
+ return formatter != null ? formatter(intValue) : intValue.toString();
+ } catch (e) {
+ return defaultValue.toString();
+ }
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return BlocBuilder(
+ builder: (context, state) {
+ final status = state.deviceStatusList;
+ final humidityValue = _getValueForStatus(
+ status,
+ 'humidity_value',
+ formatter: (value) => value.toStringAsFixed(0),
+ );
+
+ final tempValue = _getValueForStatus(
+ status,
+ 'temp_current',
+ formatter: (value) => (value / 10).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 []),
+ padding: const EdgeInsetsDirectional.all(20),
+ child: Expanded(
+ child: Column(
+ spacing: 6,
+ 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),
+ ),
+ const SizedBox(height: 20),
+ AqiSubValueWidget(
+ range: (0, 999),
+ label: AqiType.pm25.value,
+ value: pm25Value,
+ unit: AqiType.pm25.unit,
+ ),
+ AqiSubValueWidget(
+ range: (0, 999),
+ label: AqiType.pm10.value,
+ value: pm10Value,
+ unit: AqiType.pm10.unit,
+ ),
+ AqiSubValueWidget(
+ range: (0, 5),
+ label: AqiType.hcho.value,
+ value: ch2oValue,
+ unit: AqiType.hcho.unit,
+ ),
+ AqiSubValueWidget(
+ range: (0, 999),
+ label: AqiType.tvoc.value,
+ value: tvocValue,
+ unit: AqiType.tvoc.unit,
+ ),
+ AqiSubValueWidget(
+ range: (0, 5000),
+ label: AqiType.co2.value,
+ value: co2Value,
+ unit: AqiType.co2.unit,
+ ),
+ ],
+ ),
+ ),
+ );
+ },
+ );
+ }
+}
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..fc7f923a
--- /dev/null
+++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_gauge.dart
@@ -0,0 +1,115 @@
+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;
+
+ static const _minRange = 0.0;
+ static const _goodRange = 50.0;
+ static const _moderateRange = 100.0;
+ static const _maxRange = 150.0;
+
+ @override
+ Widget build(BuildContext context) {
+ final (status, statusColor) = _getStatusData(aqi);
+ return AnimatedRadialGauge(
+ value: aqi,
+ debug: false,
+ duration: const Duration(milliseconds: 500),
+ curve: Curves.easeInOut,
+ 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: 'Air Quality\n',
+ style: context.textTheme.bodySmall?.copyWith(
+ color: ColorsManager.textPrimaryColor,
+ fontWeight: FontWeight.w400,
+ fontSize: 12,
+ ),
+ children: [
+ TextSpan(
+ text: status,
+ style: context.textTheme.bodySmall?.copyWith(
+ color: _darkenColor(statusColor),
+ fontWeight: FontWeight.w400,
+ fontSize: 30,
+ ),
+ ),
+ ],
+ ),
+ textAlign: TextAlign.center,
+ ),
+ ),
+ );
+ },
+ axis: GaugeAxis(
+ progressBar: const GaugeProgressBar.basic(color: Colors.transparent),
+ style: const GaugeAxisStyle(
+ cornerRadius: Radius.circular(16),
+ thickness: 14,
+ segmentSpacing: 4,
+ ),
+ min: _minRange,
+ max: _maxRange,
+ pointer: GaugePointer.circle(
+ position: const GaugePointerPosition.surface(),
+ radius: MediaQuery.sizeOf(context).width * 0.004,
+ color: ColorsManager.whiteColors,
+ border: GaugePointerBorder(
+ width: 6,
+ color: statusColor,
+ ),
+ shadow: const BoxShadow(
+ color: ColorsManager.blackColor,
+ blurRadius: 6,
+ offset: Offset(0, 2),
+ ),
+ ),
+ segments: const [
+ GaugeSegment(
+ from: _minRange,
+ to: _goodRange,
+ cornerRadius: Radius.circular(16),
+ color: ColorsManager.goodGreen,
+ ),
+ GaugeSegment(
+ from: _goodRange + 1,
+ to: _moderateRange,
+ cornerRadius: Radius.circular(16),
+ color: ColorsManager.moderateYellow,
+ ),
+ GaugeSegment(
+ from: _moderateRange + 1,
+ to: _maxRange,
+ cornerRadius: Radius.circular(16),
+ color: ColorsManager.poorOrange,
+ ),
+ ],
+ ),
+ );
+ }
+
+ (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)!;
+ }
+}
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..7726ff32
--- /dev/null
+++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_humidity_and_temperature.dart
@@ -0,0 +1,60 @@
+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({
+ required this.temperature,
+ required this.humidity,
+ super.key,
+ });
+
+ final int temperature;
+ final int humidity;
+
+ 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: 16,
+ ),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ children: [
+ _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),
+ ],
+ );
+ }
+}
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..3f1d1f09
--- /dev/null
+++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart
@@ -0,0 +1,52 @@
+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 [],
+ borderRadius: BorderRadius.circular(10),
+ ),
+ 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,
+ ),
+ );
+ }
+}
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_location_info_cell.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart
new file mode 100644
index 00000000..fa0216a1
--- /dev/null
+++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info_cell.dart
@@ -0,0 +1,87 @@
+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: 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,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ Align(
+ alignment: AlignmentDirectional.bottomEnd,
+ child: Padding(
+ padding: const EdgeInsetsDirectional.all(10),
+ child: SizedBox(
+ height: 40,
+ width: 120,
+ child: FittedBox(
+ fit: BoxFit.scaleDown,
+ alignment: AlignmentDirectional.bottomEnd,
+ child: Text(
+ value,
+ style: context.textTheme.bodySmall?.copyWith(
+ color: ColorsManager.vividBlue.withValues(alpha: 0.7),
+ fontWeight: FontWeight.w700,
+ fontSize: 24,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ Align(
+ alignment: AlignmentDirectional.bottomStart,
+ child: SizedBox.square(
+ dimension: MediaQuery.sizeOf(context).width * 0.45,
+ child: FittedBox(
+ fit: BoxFit.scaleDown,
+ alignment: AlignmentDirectional.bottomStart,
+ child: SvgPicture.asset(svgPath),
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/pages/analytics/modules/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..5a8e6e6c
--- /dev/null
+++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_sub_value_widget.dart
@@ -0,0 +1,158 @@
+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 double max;
+ final Color color;
+}
+
+class AqiSubValueWidget extends StatelessWidget {
+ const AqiSubValueWidget({
+ required this.label,
+ required this.value,
+ required this.unit,
+ 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;
+ return [
+ _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),
+ ];
+ }
+
+ 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;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final activeSegment = _getActiveSegmentByRange(_parsedValue, range);
+ 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),
+ ],
+ ),
+ ),
+ );
+ }
+
+ 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,
+ 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: AnimatedContainer(
+ duration: const Duration(milliseconds: 150),
+ curve: Curves.linear,
+ 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,
+ ),
+ ),
+ ),
+ );
+ }
+}
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..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
@@ -3,16 +3,17 @@ 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');
+
+ const AqiType(this.value, this.unit);
final String value;
- const AqiType(this.value);
+ final String unit;
}
class AqiTypeDropdown extends StatefulWidget {
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),
+ ],
+ );
+ }
+}
diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart
index 13d51ea5..6dcf5f83 100644
--- a/lib/utils/constants/assets.dart
+++ b/lib/utils/constants/assets.dart
@@ -452,4 +452,10 @@ 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';
+ 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';
}
diff --git a/pubspec.yaml b/pubspec.yaml
index 612477fc..edd003cf 100644
--- a/pubspec.yaml
+++ b/pubspec.yaml
@@ -62,6 +62,7 @@ dependencies:
firebase_database: ^11.3.2
bloc: ^9.0.0
geocoding: ^4.0.0
+ gauge_indicator: ^0.4.3
dev_dependencies: