Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1594-device-location-api-integration

This commit is contained in:
Faris Armoush
2025-06-01 09:51:32 +03:00
24 changed files with 931 additions and 220 deletions

View File

@ -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()),
],
),
),

View File

@ -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,
};
}

View File

@ -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,
),
),
],
);
}
}

View File

@ -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<AnalyticsDevicesBloc>().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<AnalyticsDevicesBloc>().add(
SelectAnalyticsDeviceEvent(value),
);
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
context,
deviceUuid: value.uuid,
);
},
),
),
),
],
);
}
}

View File

@ -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<Status> 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<RealtimeDeviceChangesBloc, RealtimeDeviceChangesState>(
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,
),
],
),
),
);
},
);
}
}

View File

@ -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)!;
}
}

View File

@ -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),
],
);
}
}

View File

@ -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,
),
);
}
}

View File

@ -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,
),
],
),
),
],
),
);
}
}

View File

@ -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),
),
),
),
],
),
),
);
}
}

View File

@ -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,
),
),
),
);
}
}

View File

@ -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 {

View File

@ -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<AnalyticsDevice> 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,
),
),
),
),
],
),
);

View File

@ -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<AnalyticsDatePickerBloc>().state.monthlyDate,
);
},
),
const SizedBox(height: 6),
SelectableText(
context.watch<AnalyticsDevicesBloc>().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<AnalyticsDatePickerBloc>().state.monthlyDate,
);
FetchEnergyManagementDataHelper.loadRealtimeDeviceChanges(
context,
deviceUuid: value.uuid,
);
},
),
),
),
],
);
}
String _valueFromCode(String code, List<DataPoint> points) {
return points
.firstWhere((e) => e.code == code, orElse: () => DataPoint(value: '--'))

View File

@ -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<AnalyticsDevicesBloc>().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,
),
),
),
),
],
);
}
}

View File

@ -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<AnalyticsDevicesBloc>().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<AnalyticsDevicesBloc>().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),
],
);
}
}