diff --git a/.vscode/launch.json b/.vscode/launch.json
index f81a9deb..4aceb26d 100644
--- a/.vscode/launch.json
+++ b/.vscode/launch.json
@@ -16,6 +16,7 @@
"3000",
"-t",
"lib/main_dev.dart",
+ "--web-experimental-hot-reload",
],
"flutterMode": "debug"
@@ -35,6 +36,7 @@
"3000",
"-t",
"lib/main_staging.dart",
+ "--web-experimental-hot-reload",
],
"flutterMode": "debug"
@@ -54,6 +56,7 @@
"3000",
"-t",
"lib/main.dart",
+ "--web-experimental-hot-reload",
],
"flutterMode": "debug"
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/analytics/strategies/air_quality_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart
index dc3b1c5e..a8993cc3 100644
--- a/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart
+++ b/lib/pages/analytics/modules/analytics/strategies/air_quality_data_loading_strategy.dart
@@ -26,10 +26,10 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
final spaceTreeBloc = context.read();
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
- if (isSpaceSelected) {
- clearData(context);
- return;
- }
+ final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
+ if (hasSelectedSpaces) clearData(context);
+
+ if (isSpaceSelected) return;
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
@@ -42,18 +42,11 @@ final class AirQualityDataLoadingStrategy implements AnalyticsDataLoadingStrateg
);
}
- @override
- void onChildSpaceSelected(
- BuildContext context,
- CommunityModel community,
- SpaceModel child,
- ) {
- return onSpaceSelected(context, community, child);
- }
-
@override
void clearData(BuildContext context) {
- context.read().add(const SpaceTreeClearSelectionEvent());
+ context.read().add(
+ const AnalyticsClearAllSpaceTreeSelectionsEvent(),
+ );
FetchAirQualityDataHelper.clearAllData(context);
}
}
diff --git a/lib/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart
index 2c2194ba..654455b2 100644
--- a/lib/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart
+++ b/lib/pages/analytics/modules/analytics/strategies/analytics_data_loading_strategy.dart
@@ -13,10 +13,5 @@ abstract class AnalyticsDataLoadingStrategy {
CommunityModel community,
SpaceModel space,
);
- void onChildSpaceSelected(
- BuildContext context,
- CommunityModel community,
- SpaceModel child,
- );
void clearData(BuildContext context);
}
diff --git a/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart
index e73b5179..757b2a9a 100644
--- a/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart
+++ b/lib/pages/analytics/modules/analytics/strategies/energy_management_data_loading_strategy.dart
@@ -14,24 +14,14 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community,
List spaces,
) {
- context.read().add(
- OnCommunitySelected(
- community.uuid,
- spaces,
- ),
- );
+ final spaceTreeBloc = context.read();
+ final isCommunitySelected =
+ spaceTreeBloc.state.selectedCommunities.contains(community.uuid);
- final spaceTreeState = context.read().state;
- if (spaceTreeState.selectedCommunities.contains(community.uuid)) {
+ if (isCommunitySelected) {
clearData(context);
return;
}
-
- FetchEnergyManagementDataHelper.loadEnergyManagementData(
- context,
- communityId: community.uuid,
- spaceId: spaces.isNotEmpty ? spaces.first.uuid ?? '' : '',
- );
}
@override
@@ -40,21 +30,31 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
CommunityModel community,
SpaceModel space,
) {
- context.read().add(
- OnSpaceSelected(
- community,
- space.uuid ?? '',
- space.children,
- ),
- );
+ final spaceTreeBloc = context.read();
+ final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
+ final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
- final spaceTreeState = context.read().state;
- if (spaceTreeState.selectedCommunities.contains(community.uuid) ||
- spaceTreeState.selectedSpaces.contains(space.uuid)) {
- clearData(context);
+ if (isSpaceSelected) {
+ final firstSelectedSpace = spaceTreeBloc.state.selectedSpaces.first;
+ final isTheFirstSelectedSpace = firstSelectedSpace == space.uuid;
+ if (isTheFirstSelectedSpace) {
+ clearData(context);
+ }
return;
}
+ if (hasSelectedSpaces) {
+ clearData(context);
+ }
+
+ spaceTreeBloc.add(
+ OnSpaceSelected(
+ community,
+ space.uuid ?? '',
+ space.children,
+ ),
+ );
+
FetchEnergyManagementDataHelper.loadEnergyManagementData(
context,
communityId: community.uuid,
@@ -62,18 +62,11 @@ class EnergyManagementDataLoadingStrategy implements AnalyticsDataLoadingStrateg
);
}
- @override
- void onChildSpaceSelected(
- BuildContext context,
- CommunityModel community,
- SpaceModel child,
- ) {
- return onSpaceSelected(context, community, child);
- }
-
@override
void clearData(BuildContext context) {
- context.read().add(const SpaceTreeClearSelectionEvent());
+ context.read().add(
+ const AnalyticsClearAllSpaceTreeSelectionsEvent(),
+ );
FetchEnergyManagementDataHelper.clearAllData(context);
}
}
diff --git a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart
index 5241564c..9bffe3b4 100644
--- a/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart
+++ b/lib/pages/analytics/modules/analytics/strategies/occupancy_data_loading_strategy.dart
@@ -26,10 +26,10 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
final spaceTreeBloc = context.read();
final isSpaceSelected = spaceTreeBloc.state.selectedSpaces.contains(space.uuid);
- if (isSpaceSelected) {
- clearData(context);
- return;
- }
+ final hasSelectedSpaces = spaceTreeBloc.state.selectedSpaces.isNotEmpty;
+ if (hasSelectedSpaces) clearData(context);
+
+ if (isSpaceSelected) return;
spaceTreeBloc
..add(const SpaceTreeClearSelectionEvent())
@@ -42,18 +42,11 @@ class OccupancyDataLoadingStrategy implements AnalyticsDataLoadingStrategy {
);
}
- @override
- void onChildSpaceSelected(
- BuildContext context,
- CommunityModel community,
- SpaceModel child,
- ) {
- return onSpaceSelected(context, community, child);
- }
-
@override
void clearData(BuildContext context) {
- context.read().add(const SpaceTreeClearSelectionEvent());
+ context.read().add(
+ const AnalyticsClearAllSpaceTreeSelectionsEvent(),
+ );
FetchOccupancyDataHelper.clearAllData(context);
}
}
diff --git a/lib/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart b/lib/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart
index b63c6411..ab07737a 100644
--- a/lib/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart
+++ b/lib/pages/analytics/modules/analytics/widgets/analytics_communities_sidebar.dart
@@ -21,7 +21,7 @@ class AnalyticsCommunitiesSidebar extends StatelessWidget {
strategy.onSpaceSelected(context, community, space);
},
onSelectChildSpace: (community, child) {
- strategy.onChildSpaceSelected(context, community, child);
+ strategy.onSpaceSelected(context, community, child);
},
),
);
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/pages/auth/bloc/auth_bloc.dart b/lib/pages/auth/bloc/auth_bloc.dart
index 35663557..e5de46c9 100644
--- a/lib/pages/auth/bloc/auth_bloc.dart
+++ b/lib/pages/auth/bloc/auth_bloc.dart
@@ -161,7 +161,7 @@ class AuthBloc extends Bloc {
token = await AuthenticationAPI.loginWithEmail(
model: LoginWithEmailModel(
- email: event.username,
+ email: event.username.toLowerCase(),
password: event.password,
),
);
diff --git a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart
index 9083ffbe..593fdeab 100644
--- a/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart
+++ b/lib/pages/device_managment/garage_door/bloc/garage_door_bloc.dart
@@ -360,7 +360,7 @@ class GarageDoorBloc extends Bloc {
delay: deviceStatus.delay + Duration(minutes: 10));
emit(GarageDoorLoadedState(status: deviceStatus));
add(GarageDoorControlEvent(
- deviceId: event.deviceId,
+ deviceId: deviceId,
value: deviceStatus.delay.inSeconds,
code: 'countdown_1'));
} catch (e) {
diff --git a/lib/pages/routines/widgets/condition_toggle.dart b/lib/pages/routines/widgets/condition_toggle.dart
index 99ea2f04..541ad431 100644
--- a/lib/pages/routines/widgets/condition_toggle.dart
+++ b/lib/pages/routines/widgets/condition_toggle.dart
@@ -12,22 +12,53 @@ class ConditionToggle extends StatelessWidget {
});
static const _conditions = ["<", "==", ">"];
+ static const _icons = [
+ Icons.chevron_left,
+ Icons.drag_handle,
+ Icons.chevron_right
+ ];
@override
Widget build(BuildContext context) {
- return ToggleButtons(
- onPressed: (index) => onChanged(_conditions[index]),
- borderRadius: const BorderRadius.all(Radius.circular(8)),
- selectedBorderColor: ColorsManager.primaryColorWithOpacity,
- selectedColor: Colors.white,
- fillColor: ColorsManager.primaryColorWithOpacity,
- color: ColorsManager.primaryColorWithOpacity,
- constraints: const BoxConstraints(
- minHeight: 40.0,
- minWidth: 40.0,
+ final selectedIndex = _conditions.indexOf(currentCondition ?? "==");
+
+ return Container(
+ height: 30,
+ width: MediaQuery.of(context).size.width * 0.1,
+ decoration: BoxDecoration(
+ color: ColorsManager.softGray.withOpacity(0.5),
+ borderRadius: BorderRadius.circular(50),
+ ),
+ clipBehavior: Clip.antiAlias,
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: List.generate(_conditions.length, (index) {
+ final isSelected = index == selectedIndex;
+ return Expanded(
+ child: InkWell(
+ onTap: () => onChanged(_conditions[index]),
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 180),
+ curve: Curves.ease,
+ decoration: BoxDecoration(
+ color:
+ isSelected ? ColorsManager.vividBlue : Colors.transparent,
+ ),
+ child: Center(
+ child: Icon(
+ _icons[index],
+ size: 20,
+ color: isSelected
+ ? ColorsManager.whiteColors
+ : ColorsManager.blackColor,
+ weight: isSelected ? 700 : 500,
+ ),
+ ),
+ ),
+ ),
+ );
+ }),
),
- isSelected: _conditions.map((c) => c == (currentCondition ?? "==")).toList(),
- children: _conditions.map((c) => Text(c)).toList(),
);
}
}
diff --git a/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_clamp_dialog.dart b/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_clamp_dialog.dart
index c5bf8828..291abf59 100644
--- a/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_clamp_dialog.dart
+++ b/lib/pages/routines/widgets/routine_dialogs/power_clamp_enargy/energy_clamp_dialog.dart
@@ -99,7 +99,27 @@ class _EnergyClampDialogState extends State {
mainAxisSize: MainAxisSize.min,
children: [
const DialogHeader('Energy Clamp Conditions'),
- Expanded(child: _buildMainContent(context, state)),
+ Expanded(
+ child: Visibility(
+ visible: _functions.isNotEmpty,
+ replacement: SizedBox(
+ child: Row(
+ crossAxisAlignment: CrossAxisAlignment.center,
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Center(
+ child: Text(
+ 'You Cant add\n the Power Clamp to Then Section',
+ textAlign: TextAlign.center,
+ style: context.textTheme.bodyMedium!.copyWith(
+ color: ColorsManager.red,
+ fontWeight: FontWeight.w400),
+ )),
+ ],
+ ),
+ ),
+ child: _buildMainContent(context, state),
+ )),
_buildDialogFooter(context, state),
],
),
diff --git a/lib/pages/space_tree/bloc/space_tree_bloc.dart b/lib/pages/space_tree/bloc/space_tree_bloc.dart
index a3a29004..e8c2e015 100644
--- a/lib/pages/space_tree/bloc/space_tree_bloc.dart
+++ b/lib/pages/space_tree/bloc/space_tree_bloc.dart
@@ -24,6 +24,9 @@ class SpaceTreeBloc extends Bloc {
on(_fetchPaginationSpaces);
on(_onDebouncedSearch);
on(_onSpaceTreeClearSelectionEvent);
+ on(
+ _onAnalyticsClearAllSpaceTreeSelectionsEvent,
+ );
}
Timer _timer = Timer(const Duration(microseconds: 0), () {});
@@ -493,6 +496,20 @@ class SpaceTreeBloc extends Bloc {
);
}
+ void _onAnalyticsClearAllSpaceTreeSelectionsEvent(
+ AnalyticsClearAllSpaceTreeSelectionsEvent event,
+ Emitter emit,
+ ) async {
+ emit(
+ state.copyWith(
+ selectedCommunities: [],
+ selectedCommunityAndSpaces: {},
+ selectedSpaces: [],
+ soldCheck: [],
+ ),
+ );
+ }
+
@override
Future close() async {
_timer.cancel();
diff --git a/lib/pages/space_tree/bloc/space_tree_event.dart b/lib/pages/space_tree/bloc/space_tree_event.dart
index 9c2342fc..6e1687af 100644
--- a/lib/pages/space_tree/bloc/space_tree_event.dart
+++ b/lib/pages/space_tree/bloc/space_tree_event.dart
@@ -112,3 +112,7 @@ class ClearCachedData extends SpaceTreeEvent {}
class SpaceTreeClearSelectionEvent extends SpaceTreeEvent {
const SpaceTreeClearSelectionEvent();
}
+
+final class AnalyticsClearAllSpaceTreeSelectionsEvent extends SpaceTreeEvent {
+ const AnalyticsClearAllSpaceTreeSelectionsEvent();
+}
diff --git a/lib/utils/constants/assets.dart b/lib/utils/constants/assets.dart
index 515ede28..5eb0eb05 100644
--- a/lib/utils/constants/assets.dart
+++ b/lib/utils/constants/assets.dart
@@ -452,9 +452,17 @@ class Assets {
'assets/icons/refresh_status_icon.svg';
static const String energyConsumedIcon =
'assets/icons/energy_consumed_icon.svg';
+
static const String closeSettingsIcon =
'assets/icons/close_settings_icon.svg';
static const String editNameIconSettings =
'assets/icons/edit_name_icon_settings.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 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: