Compare commits

..

21 Commits

Author SHA1 Message Date
24a7f3ac2a SP-1594-device-location-api-integration. 2025-06-04 13:06:27 +03:00
79b974ee6c re-injected AirQualityDistributionBloc into AnalyticsPage. 2025-06-04 09:36:41 +03:00
651ac6785e removed open weather api keys from .env files. 2025-06-04 09:32:56 +03:00
9fa59ce78b Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1594-device-location-api-integration 2025-06-04 09:29:49 +03:00
e2c44ba85f injected the remote and reverse geocoder dependenies into DeviceLocationBloc. 2025-06-04 09:28:50 +03:00
1edeb664aa Connected data coming from DeviceLocationBloc into the respective widgets. 2025-06-04 09:28:16 +03:00
25a55ad820 made GetDeviceLocationDataParam.toJson method have the correct keys for the API. 2025-06-04 09:27:46 +03:00
e48fc8b82c loads and clears DeviceLocationBloc. 2025-06-04 09:27:21 +03:00
8d999f118c Connected RemoteDeviceLocationService to the new BE API, instead of directly fetching the data from OpenWeather Api's. 2025-06-04 09:18:28 +03:00
bcb6e49a01 Deleted FakeDeviceLocationService class, since it is no longer needed. 2025-06-04 09:17:38 +03:00
b0ed844893 made events and state class finals, to better document that they shouldn't be extended. 2025-06-01 14:37:22 +03:00
3d183528c5 Fixed thrown exceptions because of Expanded widgets. 2025-06-01 10:57:49 +03:00
2c4da63266 Injected DeviceLocationBloc into AnalyticsPage. 2025-06-01 10:50:51 +03:00
4ebe65f820 Merge branch 'dev' of https://github.com/SyncrowIOT/web into SP-1594-device-location-api-integration 2025-06-01 09:51:32 +03:00
5654d66b60 Created a remote implementation for DeviceLocationService. 2025-06-01 09:51:08 +03:00
b6879035f0 Implemented geocoding functionality to retrieve and manage device location data using the newly added geocoding package. 2025-05-29 15:47:34 +03:00
8ad048e18d Added geocoding: ^4.0.0 package. 2025-05-29 15:47:24 +03:00
d92b699a2b Created a bloc for loading and managing the state of device location data. 2025-05-29 15:40:44 +03:00
6ffb677c33 Created an interface and its fake implementation for loading device location data. 2025-05-29 15:40:15 +03:00
e7476a084d Created a model class for loading device location data. 2025-05-29 15:39:51 +03:00
511acc186f Created a param class for loading device location data. 2025-05-29 15:39:44 +03:00
34 changed files with 487 additions and 426 deletions

View File

@ -1,24 +0,0 @@
<svg width="40" height="26" viewBox="0 0 40 26" fill="none" xmlns="http://www.w3.org/2000/svg">
<g filter="url(#filter0_d_9292_1537)">
<rect x="3" y="3" width="34" height="20" rx="10" fill="#F5F6F7"/>
<g clip-path="url(#clip0_9292_1537)">
<path d="M20.4393 20H19.5607C18.85 20 18.2718 19.4218 18.2718 18.7112V18.414C17.9697 18.3174 17.6762 18.1956 17.3942 18.0497L17.1835 18.2603C16.6733 18.7711 15.8561 18.7562 15.3607 18.2601L14.7397 17.6391C14.2434 17.1434 14.2291 16.3264 14.7398 15.8163L14.9503 15.6058C14.8044 15.3238 14.6826 15.0303 14.586 14.7281H14.2888C13.5782 14.7281 13 14.15 13 13.4393V12.5607C13 11.85 13.5782 11.2719 14.2888 11.2719H14.586C14.6826 10.9697 14.8044 10.6762 14.9503 10.3942L14.7397 10.1836C14.2293 9.67374 14.2434 8.85666 14.7399 8.36072L15.3609 7.73969C15.8574 7.24247 16.6745 7.23006 17.1838 7.73986L17.3942 7.95032C17.6762 7.80441 17.9698 7.68257 18.2719 7.58602V7.28879C18.2719 6.57816 18.85 6 19.5607 6H20.4393C21.15 6 21.7281 6.57816 21.7281 7.28879V7.58605C22.0302 7.68257 22.3238 7.80441 22.6058 7.95035L22.8164 7.73969C23.3266 7.22886 24.1439 7.24384 24.6393 7.73988L25.2603 8.36086C25.7566 8.85657 25.7708 9.67358 25.2601 10.1837L25.0497 10.3942C25.1956 10.6762 25.3174 10.9697 25.414 11.2719H25.7112C26.4218 11.2719 27 11.85 27 12.5607V13.4393C27 14.15 26.4218 14.7281 25.7112 14.7281H25.414C25.3174 15.0303 25.1956 15.3238 25.0497 15.6058L25.2603 15.8164C25.7707 16.3263 25.7566 17.1434 25.2601 17.6393L24.6391 18.2603C24.1426 18.7576 23.3255 18.77 22.8162 18.2602L22.6058 18.0497C22.3238 18.1956 22.0302 18.3175 21.7281 18.414V18.7113C21.7281 19.4218 21.15 20 20.4393 20ZM17.5313 17.1882C17.9231 17.4199 18.3447 17.595 18.7845 17.7085C18.9656 17.7552 19.0922 17.9185 19.0922 18.1056V18.7112C19.0922 18.9695 19.3024 19.1797 19.5607 19.1797H20.4393C20.6976 19.1797 20.9078 18.9695 20.9078 18.7112V18.1056C20.9078 17.9185 21.0344 17.7552 21.2155 17.7085C21.6553 17.595 22.0769 17.4199 22.4687 17.1882C22.6299 17.0929 22.8351 17.1188 22.9675 17.2513L23.3965 17.6803C23.5815 17.8654 23.8785 17.8611 24.0589 17.6805L24.6803 17.059C24.8603 16.8793 24.8663 16.5822 24.6805 16.3966L24.2513 15.9675C24.1189 15.8351 24.093 15.6298 24.1883 15.4687C24.42 15.0769 24.595 14.6553 24.7085 14.2155C24.7552 14.0344 24.9186 13.9078 25.1056 13.9078H25.7112C25.9695 13.9078 26.1797 13.6977 26.1797 13.4394V12.5607C26.1797 12.3024 25.9695 12.0922 25.7112 12.0922H25.1056C24.9186 12.0922 24.7552 11.9657 24.7085 11.7846C24.595 11.3447 24.42 10.9231 24.1883 10.5314C24.093 10.3702 24.1189 10.165 24.2513 10.0326L24.6803 9.60358C24.8658 9.41835 24.8609 9.1214 24.6805 8.94118L24.0591 8.31979C23.879 8.13943 23.5819 8.13415 23.3967 8.31962L22.9676 8.74879C22.8352 8.88121 22.6299 8.90713 22.4687 8.81181C22.077 8.58013 21.6553 8.4051 21.2155 8.2916C21.0344 8.24487 20.9079 8.08152 20.9079 7.89446V7.28879C20.9079 7.03048 20.6977 6.82031 20.4394 6.82031H19.5607C19.3024 6.82031 19.0922 7.03048 19.0922 7.28879V7.8944C19.0922 8.08146 18.9657 8.24481 18.7845 8.29154C18.3447 8.40505 17.9231 8.58007 17.5314 8.81176C17.3701 8.90705 17.1649 8.88113 17.0325 8.74873L16.6036 8.31973C16.4186 8.13456 16.1216 8.13886 15.9412 8.31954L15.3197 8.94096C15.1398 9.12071 15.1337 9.41775 15.3196 9.60336L15.7487 10.0325C15.8811 10.1649 15.9071 10.3702 15.8118 10.5313C15.5801 10.9231 15.4051 11.3447 15.2916 11.7845C15.2448 11.9656 15.0815 12.0922 14.8944 12.0922H14.2888C14.0305 12.0922 13.8203 12.3024 13.8203 12.5607V13.4393C13.8203 13.6976 14.0305 13.9078 14.2888 13.9078H14.8944C15.0815 13.9078 15.2448 14.0344 15.2915 14.2155C15.405 14.6553 15.5801 15.0769 15.8117 15.4686C15.907 15.6298 15.8811 15.8351 15.7487 15.9675L15.3197 16.3965C15.1343 16.5817 15.1391 16.8786 15.3195 17.0589L15.9409 17.6802C16.121 17.8606 16.4181 17.8659 16.6033 17.6804L17.0325 17.2512C17.13 17.1537 17.333 17.0709 17.5313 17.1882Z" fill="#023DFE" fill-opacity="0.7"/>
<path d="M19.9992 16.0461C18.3196 16.0461 16.9531 14.6796 16.9531 13C16.9531 11.3204 18.3196 9.95391 19.9992 9.95391C21.6789 9.95391 23.0453 11.3204 23.0453 13C23.0453 14.6796 21.6789 16.0461 19.9992 16.0461ZM19.9992 10.7742C18.7719 10.7742 17.7734 11.7727 17.7734 13C17.7734 14.2273 18.7719 15.2258 19.9992 15.2258C21.2265 15.2258 22.225 14.2273 22.225 13C22.225 11.7727 21.2265 10.7742 19.9992 10.7742Z" fill="#023DFE" fill-opacity="0.7"/>
</g>
</g>
<defs>
<filter id="filter0_d_9292_1537" x="0" y="0" width="40" height="26" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0" result="hardAlpha"/>
<feOffset/>
<feGaussianBlur stdDeviation="1.5"/>
<feComposite in2="hardAlpha" operator="out"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_9292_1537"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_9292_1537" result="shape"/>
</filter>
<clipPath id="clip0_9292_1537">
<rect width="14" height="14" fill="white" transform="translate(13 6)"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 5.0 KiB

View File

@ -8,6 +8,8 @@ class AnalyticsDevice {
this.isActive,
this.productDevice,
this.spaceUuid,
this.latitude,
this.longitude,
});
final String uuid;
@ -18,6 +20,8 @@ class AnalyticsDevice {
final bool? isActive;
final ProductDevice? productDevice;
final String? spaceUuid;
final double? latitude;
final double? longitude;
factory AnalyticsDevice.fromJson(Map<String, dynamic> json) {
return AnalyticsDevice(
@ -35,6 +39,8 @@ class AnalyticsDevice {
? ProductDevice.fromJson(json['productDevice'] as Map<String, dynamic>)
: null,
spaceUuid: json['spaceUuid'] as String?,
latitude: json['lat'] != null ? double.parse(json['lat'] as String) : null,
longitude: json['lon'] != null ? double.parse(json['lon'] as String) : null,
);
}
}

View File

@ -0,0 +1,55 @@
import 'package:equatable/equatable.dart';
class DeviceLocationInfo extends Equatable {
const DeviceLocationInfo({
this.airQuality,
this.humidity,
this.city,
this.country,
this.address,
this.temperature,
});
final double? airQuality;
final double? humidity;
final String? city;
final String? country;
final String? address;
final double? temperature;
factory DeviceLocationInfo.fromJson(Map<String, dynamic> json) {
return DeviceLocationInfo(
airQuality: json['aqi'] as double?,
humidity: json['humidity'] as double?,
temperature: json['temperature'] as double?,
);
}
DeviceLocationInfo copyWith({
double? airQuality,
double? humidity,
String? city,
String? country,
String? address,
double? temperature,
}) {
return DeviceLocationInfo(
airQuality: airQuality ?? this.airQuality,
humidity: humidity ?? this.humidity,
city: city ?? this.city,
country: country ?? this.country,
address: address ?? this.address,
temperature: temperature ?? this.temperature,
);
}
@override
List<Object?> get props => [
airQuality,
humidity,
city,
country,
address,
temperature,
];
}

View File

@ -0,0 +1,50 @@
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/device_location_info.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_service.dart';
part 'device_location_event.dart';
part 'device_location_state.dart';
class DeviceLocationBloc extends Bloc<DeviceLocationEvent, DeviceLocationState> {
DeviceLocationBloc(
this._deviceLocationService,
) : super(const DeviceLocationState()) {
on<LoadDeviceLocationEvent>(_onLoadDeviceLocation);
on<ClearDeviceLocationEvent>(_onClearDeviceLocation);
}
final DeviceLocationService _deviceLocationService;
Future<void> _onLoadDeviceLocation(
LoadDeviceLocationEvent event,
Emitter<DeviceLocationState> emit,
) async {
emit(const DeviceLocationState(status: DeviceLocationStatus.loading));
try {
final locationInfo = await _deviceLocationService.get(event.param);
emit(
DeviceLocationState(
status: DeviceLocationStatus.success,
locationInfo: locationInfo,
),
);
} catch (e) {
emit(
DeviceLocationState(
status: DeviceLocationStatus.failure,
errorMessage: e.toString(),
),
);
}
}
void _onClearDeviceLocation(
ClearDeviceLocationEvent event,
Emitter<DeviceLocationState> emit,
) {
emit(const DeviceLocationState());
}
}

View File

@ -0,0 +1,21 @@
part of 'device_location_bloc.dart';
sealed class DeviceLocationEvent extends Equatable {
const DeviceLocationEvent();
@override
List<Object?> get props => [];
}
final class LoadDeviceLocationEvent extends DeviceLocationEvent {
const LoadDeviceLocationEvent(this.param);
final GetDeviceLocationDataParam param;
@override
List<Object?> get props => [param];
}
final class ClearDeviceLocationEvent extends DeviceLocationEvent {
const ClearDeviceLocationEvent();
}

View File

@ -0,0 +1,18 @@
part of 'device_location_bloc.dart';
enum DeviceLocationStatus { initial, loading, success, failure }
final class DeviceLocationState extends Equatable {
const DeviceLocationState({
this.status = DeviceLocationStatus.initial,
this.locationInfo,
this.errorMessage,
});
final DeviceLocationStatus status;
final DeviceLocationInfo? locationInfo;
final String? errorMessage;
@override
List<Object?> get props => [status, locationInfo, errorMessage];
}

View File

@ -1,12 +1,14 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.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/realtime_device_changes/realtime_device_changes_bloc.dart';
import 'package:syncrow_web/pages/analytics/params/get_air_quality_distribution_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
import 'package:syncrow_web/pages/analytics/params/get_range_of_aqi_param.dart';
abstract final class FetchAirQualityDataHelper {
@ -48,6 +50,8 @@ abstract final class FetchAirQualityDataHelper {
const ClearAirQualityDistribution(),
);
context.read<RangeOfAqiBloc>().add(const ClearRangeOfAqiEvent());
context.read<DeviceLocationBloc>().add(const ClearDeviceLocationEvent());
}
static void loadAnalyticsDevices(
@ -61,12 +65,21 @@ abstract final class FetchAirQualityDataHelper {
communityUuid: communityUuid,
spaceUuid: spaceUuid,
deviceTypes: ['AQI'],
requestType: AnalyticsDeviceRequestType.energyManagement,
requestType: AnalyticsDeviceRequestType.occupancy,
),
onSuccess: (device) {
context.read<RealtimeDeviceChangesBloc>()
..add(const RealtimeDeviceChangesClosed())
..add(RealtimeDeviceChangesStarted(device.uuid));
context.read<DeviceLocationBloc>().add(
LoadDeviceLocationEvent(
GetDeviceLocationDataParam(
latitude: device.latitude ?? 0,
longitude: device.longitude ?? 0,
),
),
);
},
),
);

View File

@ -72,55 +72,54 @@ class AqiDeviceInfo extends StatelessWidget {
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,
),
],
),
child: Column(
spacing: 6,
mainAxisSize: MainAxisSize.max,
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

@ -6,7 +6,34 @@ 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});
const AqiLocation({
required this.city,
required this.country,
required this.address,
super.key,
});
final String? city;
final String? country;
final String? address;
String _getFormattedLocation() {
if (city == null && country == null && address == null) {
return 'N/A';
}
final parts = <String>[];
if (city != null) parts.add(city!);
if (address != null) parts.add(address!);
final locationPart = parts.join(', ');
if (country != null) {
return locationPart.isEmpty ? country! : '$locationPart - $country';
}
return locationPart;
}
@override
Widget build(BuildContext context) {
@ -24,7 +51,7 @@ class AqiLocation extends StatelessWidget {
_buildLocationPin(),
Expanded(
child: Text(
'Business Bay, Dubai - UAE',
_getFormattedLocation(),
style: context.textTheme.bodySmall?.copyWith(
color: ColorsManager.textPrimaryColor,
fontWeight: FontWeight.w400,

View File

@ -1,4 +1,6 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.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';
@ -9,37 +11,46 @@ class AqiLocationInfo extends StatelessWidget {
@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,
return BlocBuilder<DeviceLocationBloc, DeviceLocationState>(
builder: (context, state) {
final info = state.locationInfo;
return Container(
decoration: secondarySection.copyWith(boxShadow: const []),
padding: const EdgeInsetsDirectional.all(20),
child: Column(
spacing: 8,
children: [
AqiLocation(
city: info?.city,
country: info?.country,
address: info?.address,
),
Expanded(
child: Row(
spacing: 8,
children: [
AqiLocationInfoCell(
label: 'Temperature',
value: ' ${info?.temperature?.roundToDouble() ?? '--'}°',
svgPath: Assets.aqiTemperature,
),
AqiLocationInfoCell(
label: 'Humidity',
value: '${info?.humidity?.roundToDouble() ?? '--'}%',
svgPath: Assets.aqiHumidity,
),
AqiLocationInfoCell(
label: 'Air Quality',
value: ' ${info?.airQuality?.roundToDouble() ?? '--'}',
svgPath: Assets.aqiAirQuality,
),
],
),
AqiLocationInfoCell(
label: 'Humidity',
value: '25%',
svgPath: Assets.aqiHumidity,
),
AqiLocationInfoCell(
label: 'Air Quality',
value: ' 120',
svgPath: Assets.aqiAirQuality,
),
],
),
),
],
),
],
),
);
},
);
}
}

View File

@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'analytics_devices_event.dart';
part 'analytics_devices_state.dart';
@ -37,13 +36,6 @@ class AnalyticsDevicesBloc
if (devices.isNotEmpty) {
event.onSuccess(devices.first);
}
} on APIException catch (e) {
emit(
AnalyticsDevicesState(
status: AnalyticsDevicesStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
AnalyticsDevicesState(

View File

@ -1,6 +1,8 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/air_quality_distribution/air_quality_distribution_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart';
import 'package:syncrow_web/pages/analytics/modules/air_quality/blocs/range_of_aqi/range_of_aqi_bloc.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';
@ -18,6 +20,8 @@ import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/fa
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service_delagate.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_energy_management_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/remote_occupancy_analytics_devices_service.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_details_service_decorator.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/remote_device_location_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/remote_energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/remote_energy_consumption_per_device_service.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/remote_occupancy_service.dart';
@ -108,6 +112,18 @@ class _AnalyticsPageState extends State<AnalyticsPage> {
FakeAirQualityDistributionService(),
),
),
BlocProvider(
create: (context) => DeviceLocationBloc(
DeviceLocationDetailsServiceDecorator(
RemoteDeviceLocationService(_httpService),
Dio(
BaseOptions(
baseUrl: 'https://nominatim.openstreetmap.org/',
),
),
),
),
),
],
child: const AnalyticsPageForm(),
);

View File

@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'energy_consumption_by_phases_event.dart';
part 'energy_consumption_by_phases_state.dart';
@ -32,13 +31,6 @@ class EnergyConsumptionByPhasesBloc
chartData: chartData,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: EnergyConsumptionByPhasesStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'energy_consumption_per_device_event.dart';
part 'energy_consumption_per_device_state.dart';
@ -14,8 +13,7 @@ class EnergyConsumptionPerDeviceBloc
this._energyConsumptionPerDeviceService,
) : super(const EnergyConsumptionPerDeviceState()) {
on<LoadEnergyConsumptionPerDeviceEvent>(_onLoadEnergyConsumptionPerDeviceEvent);
on<ClearEnergyConsumptionPerDeviceEvent>(
_onClearEnergyConsumptionPerDeviceEvent);
on<ClearEnergyConsumptionPerDeviceEvent>(_onClearEnergyConsumptionPerDeviceEvent);
}
final EnergyConsumptionPerDeviceService _energyConsumptionPerDeviceService;
@ -33,13 +31,6 @@ class EnergyConsumptionPerDeviceBloc
chartData: chartData,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: EnergyConsumptionPerDeviceStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
import 'package:syncrow_web/pages/device_managment/all_devices/models/device_status.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'power_clamp_info_event.dart';
part 'power_clamp_info_state.dart';
@ -32,13 +31,6 @@ class PowerClampInfoBloc extends Bloc<PowerClampInfoEvent, PowerClampInfoState>
powerClampModel: powerClampModel,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: PowerClampInfoStatus.error,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'total_energy_consumption_event.dart';
part 'total_energy_consumption_state.dart';
@ -32,13 +31,6 @@ class TotalEnergyConsumptionBloc
status: TotalEnergyConsumptionStatus.loaded,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
errorMessage: e.message,
status: TotalEnergyConsumptionStatus.failure,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'occupancy_event.dart';
part 'occupancy_state.dart';
@ -24,8 +23,6 @@ class OccupancyBloc extends Bloc<OccupancyEvent, OccupancyState> {
try {
final chartData = await _occupacyService.load(event.param);
emit(state.copyWith(chartData: chartData, status: OccupancyStatus.loaded));
} on APIException catch (e) {
emit(state.copyWith(status: OccupancyStatus.failure, errorMessage: e.message));
} catch (e) {
emit(state.copyWith(status: OccupancyStatus.failure, errorMessage: '$e'));
}

View File

@ -3,7 +3,6 @@ import 'package:equatable/equatable.dart';
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
part 'occupancy_heat_map_event.dart';
part 'occupancy_heat_map_state.dart';
@ -31,13 +30,6 @@ class OccupancyHeatMapBloc
heatMapData: occupancyHeatMap,
),
);
} on APIException catch (e) {
emit(
state.copyWith(
status: OccupancyHeatMapStatus.failure,
errorMessage: e.message,
),
);
} catch (e) {
emit(
state.copyWith(

View File

@ -0,0 +1,11 @@
class GetDeviceLocationDataParam {
const GetDeviceLocationDataParam({
required this.latitude,
required this.longitude,
});
final double latitude;
final double longitude;
Map<String, dynamic> toJson() => {'lat': latitude, 'lon': longitude};
}

View File

@ -1,8 +1,6 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteEnergyManagementAnalyticsDevicesService
@ -11,8 +9,6 @@ final class RemoteEnergyManagementAnalyticsDevicesService
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load analytics devices';
@override
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
try {
@ -33,14 +29,8 @@ final class RemoteEnergyManagementAnalyticsDevicesService
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw APIException('$_defaultErrorMessage: $e');
throw Exception('Failed to load total energy consumption: $e');
}
}
}

View File

@ -1,9 +1,7 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/analytics_device.dart';
import 'package:syncrow_web/pages/analytics/params/get_analytics_devices_param.dart';
import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics_devices_service.dart';
import 'package:syncrow_web/pages/common/bloc/project_manager.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService {
@ -11,8 +9,6 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load analytics devices';
@override
Future<List<AnalyticsDevice>> getDevices(GetAnalyticsDevicesParam param) async {
try {
@ -30,15 +26,8 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService
final result = requests.map((e) => e.first).toList();
return result;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, e.toString()].join(': ');
throw APIException(formattedErrorMessage);
throw Exception('Failed to load total energy consumption: $e');
}
}
@ -65,14 +54,8 @@ class RemoteOccupancyAnalyticsDevicesService implements AnalyticsDevicesService
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
throw APIException('$_defaultErrorMessage: $e');
rethrow;
}
}
}

View File

@ -0,0 +1,40 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/device_location_info.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_service.dart';
class DeviceLocationDetailsServiceDecorator implements DeviceLocationService {
const DeviceLocationDetailsServiceDecorator(this._decoratee, this._dio);
final DeviceLocationService _decoratee;
final Dio _dio;
@override
Future<DeviceLocationInfo> get(GetDeviceLocationDataParam param) async {
try {
final deviceLocationInfo = await _decoratee.get(param);
final response = await _dio.get<Map<String, dynamic>>(
'reverse',
queryParameters: {
'format': 'json',
'lat': param.latitude,
'lon': param.longitude,
},
);
final data = response.data;
if (data != null) {
final addressData = data['address'] as Map<String, dynamic>;
return deviceLocationInfo.copyWith(
city: addressData['city'],
country: addressData['country_code'].toString().toUpperCase(),
address: addressData['state'],
);
}
return deviceLocationInfo;
} catch (e) {
throw Exception('Failed to load device location info: ${e.toString()}');
}
}
}

View File

@ -0,0 +1,6 @@
import 'package:syncrow_web/pages/analytics/models/device_location_info.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
abstract interface class DeviceLocationService {
Future<DeviceLocationInfo> get(GetDeviceLocationDataParam param);
}

View File

@ -0,0 +1,37 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/device_location_info.dart';
import 'package:syncrow_web/pages/analytics/params/get_device_location_data_param.dart';
import 'package:syncrow_web/pages/analytics/services/device_location/device_location_service.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteDeviceLocationService implements DeviceLocationService {
const RemoteDeviceLocationService(this._httpService);
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load device location';
@override
Future<DeviceLocationInfo> get(GetDeviceLocationDataParam param) async {
try {
final response = await _httpService.get(
path: '/weather',
queryParameters: param.toJson(),
expectedResponseModel: (data) {
final response = data as Map<String, dynamic>;
final location = response['data'] as Map<String, dynamic>;
return DeviceLocationInfo.fromJson(location);
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
throw Exception(errorMessage);
} catch (e) {
throw Exception('$_defaultErrorMessage: $e');
}
}
}

View File

@ -1,8 +1,6 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/phases_energy_consumption.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_by_phases_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_by_phases/energy_consumption_by_phases_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteEnergyConsumptionByPhasesService
@ -11,8 +9,6 @@ final class RemoteEnergyConsumptionByPhasesService
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load energy consumption per phase';
@override
Future<List<PhasesEnergyConsumption>> load(
GetEnergyConsumptionByPhasesParam param,
@ -32,15 +28,8 @@ final class RemoteEnergyConsumptionByPhasesService
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
throw Exception('Failed to load energy consumption per phase: $e');
}
}
}

View File

@ -1,10 +1,8 @@
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:syncrow_web/pages/analytics/models/device_energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_energy_consumption_per_device_param.dart';
import 'package:syncrow_web/pages/analytics/services/energy_consumption_per_device/energy_consumption_per_device_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteEnergyConsumptionPerDeviceService
@ -13,8 +11,6 @@ class RemoteEnergyConsumptionPerDeviceService
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load energy consumption per device';
@override
Future<List<DeviceEnergyDataModel>> load(
GetEnergyConsumptionPerDeviceParam param,
@ -27,15 +23,8 @@ class RemoteEnergyConsumptionPerDeviceService
expectedResponseModel: _EnergyConsumptionPerDeviceMapper.map,
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
throw Exception('Failed to load energy consumption per device: $e');
}
}
}

View File

@ -1,8 +1,6 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/occupacy.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupacy/occupacy_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyService implements OccupacyService {
@ -10,8 +8,6 @@ final class RemoteOccupancyService implements OccupacyService {
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load occupancy';
@override
Future<List<Occupacy>> load(GetOccupancyParam param) async {
try {
@ -29,15 +25,8 @@ final class RemoteOccupancyService implements OccupacyService {
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
throw Exception('Failed to load energy consumption per phase: $e');
}
}
}
}

View File

@ -1,8 +1,6 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/occupancy_heat_map_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_occupancy_heat_map_param.dart';
import 'package:syncrow_web/pages/analytics/services/occupancy_heat_map/occupancy_heat_map_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
@ -10,8 +8,6 @@ final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load occupancy heat map';
@override
Future<List<OccupancyHeatMapModel>> load(GetOccupancyHeatMapParam param) async {
try {
@ -32,15 +28,8 @@ final class RemoteOccupancyHeatMapService implements OccupancyHeatMapService {
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
throw Exception('Failed to load total energy consumption:');
}
}
}

View File

@ -1,7 +1,5 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/services/power_clamp_info/power_clamp_info_service.dart';
import 'package:syncrow_web/pages/device_managment/power_clamp/models/power_clamp_model.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
final class RemotePowerClampInfoService implements PowerClampInfoService {
@ -9,8 +7,6 @@ final class RemotePowerClampInfoService implements PowerClampInfoService {
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to fetch power clamp info';
@override
Future<PowerClampModel> getInfo(String deviceId) async {
try {
@ -24,15 +20,8 @@ final class RemotePowerClampInfoService implements PowerClampInfoService {
},
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
throw Exception('Failed to fetch power clamp info: $e');
}
}
}

View File

@ -1,8 +1,6 @@
import 'package:dio/dio.dart';
import 'package:syncrow_web/pages/analytics/models/energy_data_model.dart';
import 'package:syncrow_web/pages/analytics/params/get_total_energy_consumption_param.dart';
import 'package:syncrow_web/pages/analytics/services/total_energy_consumption/total_energy_consumption_service.dart';
import 'package:syncrow_web/services/api/api_exception.dart';
import 'package:syncrow_web/services/api/http_service.dart';
class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionService {
@ -10,8 +8,6 @@ class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionServi
final HTTPService _httpService;
static const _defaultErrorMessage = 'Failed to load total energy consumption';
@override
Future<List<EnergyDataModel>> load(
GetTotalEnergyConsumptionParam param,
@ -25,15 +21,8 @@ class RemoteTotalEnergyConsumptionService implements TotalEnergyConsumptionServi
);
return response;
} on DioException catch (e) {
final message = e.response?.data as Map<String, dynamic>?;
final error = message?['error'] as Map<String, dynamic>?;
final errorMessage = error?['error'] as String? ?? '';
final formattedErrorMessage = [_defaultErrorMessage, errorMessage].join(': ');
throw APIException(formattedErrorMessage);
} catch (e) {
final formattedErrorMessage = [_defaultErrorMessage, '$e'].join(': ');
throw APIException(formattedErrorMessage);
throw Exception('Failed to load total energy consumption: $e');
}
}
}

View File

@ -162,34 +162,31 @@ class _DynamicTableState extends State<DynamicTable> {
child: SingleChildScrollView(
scrollDirection: Axis.horizontal,
controller: _horizontalBodyScrollController,
child: Container(
color: ColorsManager.whiteColors,
child: SizedBox(
width: widget.size.width,
child: widget.isEmpty
? _buildEmptyState()
: Column(
children: List.generate(widget.data.length,
(rowIndex) {
final row = widget.data[rowIndex];
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(rowIndex,
widget.size.height * 0.08),
...row.asMap().entries.map((entry) {
return _buildTableCell(
entry.value.toString(),
widget.size.height * 0.08,
rowIndex: rowIndex,
columnIndex: entry.key,
);
}).toList(),
],
);
}),
),
),
child: SizedBox(
width: widget.size.width,
child: widget.isEmpty
? _buildEmptyState()
: Column(
children:
List.generate(widget.data.length, (rowIndex) {
final row = widget.data[rowIndex];
return Row(
children: [
if (widget.withCheckBox)
_buildRowCheckbox(
rowIndex, widget.size.height * 0.08),
...row.asMap().entries.map((entry) {
return _buildTableCell(
entry.value.toString(),
widget.size.height * 0.08,
rowIndex: rowIndex,
columnIndex: entry.key,
);
}).toList(),
],
);
}),
),
),
),
),
@ -214,6 +211,7 @@ class _DynamicTableState extends State<DynamicTable> {
onChanged: widget.withSelectAll && widget.data.isNotEmpty
? _toggleSelectAll
: null,
),
);
}
@ -284,6 +282,7 @@ class _DynamicTableState extends State<DynamicTable> {
padding: EdgeInsets.symmetric(
horizontal: index == widget.headers.length - 1 ? 12 : 8.0,
vertical: 4),
child: Text(
title,
style: context.textTheme.titleSmall!.copyWith(
@ -304,6 +303,7 @@ class _DynamicTableState extends State<DynamicTable> {
required int rowIndex,
required int columnIndex,
}) {
bool isBatteryLevel = content.endsWith('%');
double? batteryLevel;
@ -313,13 +313,9 @@ class _DynamicTableState extends State<DynamicTable> {
bool isSettingsColumn = widget.headers[columnIndex] == 'Settings';
if (isSettingsColumn) {
return buildSettingsIcon(
width: 120,
height: 60,
iconSize: 40,
onTap: () => widget.onSettingsPressed?.call(rowIndex),
);
return _buildSettingsIcon(rowIndex, size);
}
Color? statusColor;
switch (content) {
@ -372,63 +368,22 @@ class _DynamicTableState extends State<DynamicTable> {
);
}
Widget buildSettingsIcon(
{double width = 120,
double height = 60,
double iconSize = 40,
VoidCallback? onTap}) {
return Column(
children: [
Container(
padding: const EdgeInsets.only(top: 10, bottom: 15, left: 10),
margin: const EdgeInsets.only(right: 15),
decoration: const BoxDecoration(
color: ColorsManager.whiteColors,
border: Border(
bottom: BorderSide(
color: ColorsManager.boxDivider,
width: 1.0,
),
),
),
width: width,
child: Padding(
padding: const EdgeInsets.only(
right: 16.0,
left: 17.0,
),
child: Container(
width: 50,
decoration: BoxDecoration(
color: const Color(0xFFF7F8FA),
borderRadius: BorderRadius.circular(height / 2),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.17),
blurRadius: 14,
offset: const Offset(0, 4),
),
],
),
child: InkWell(
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: SvgPicture.asset(
Assets.settings, // ضع المسار الصحيح هنا
width: 40,
height: 22,
color: ColorsManager
.primaryColor, // نفس لون الأيقونة في الصورة
),
),
),
),
),
),
Widget _buildSettingsIcon(int rowIndex, double size) {
return Container(
height: size,
width: 120,
padding: const EdgeInsets.all(5.0),
decoration: const BoxDecoration(
border: Border(
bottom: BorderSide(color: ColorsManager.boxDivider, width: 1.0),
),
],
color: Colors.white,
),
alignment: Alignment.center,
child: IconButton(
icon: SvgPicture.asset(Assets.settings),
onPressed: () => widget.onSettingsPressed?.call(rowIndex),
),
);
}
}

View File

@ -109,12 +109,12 @@ class SaveRoutineHelper {
spacing: 16,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
DialogFooterButton(
text: 'Back',
DialogFooterButton(
text: 'Cancel',
onTap: () => Navigator.pop(context),
),
DialogFooterButton(
text: 'Save',
text: 'Confirm',
onTap: () {
if (state.isAutomation) {
if (state.isUpdate ?? false) {

View File

@ -14,8 +14,7 @@ class Assets {
static const String rightLine = "assets/images/right_line.png";
static const String google = "assets/images/google.svg";
static const String facebook = "assets/images/facebook.svg";
static const String invisiblePassword =
"assets/images/Password_invisible.svg";
static const String invisiblePassword = "assets/images/Password_invisible.svg";
static const String visiblePassword = "assets/images/password_visible.svg";
static const String accessIcon = "assets/images/access_icon.svg";
static const String spaseManagementIcon =
@ -34,8 +33,7 @@ class Assets {
static const String emptyTable = "assets/images/empty_table.svg";
// General assets
static const String motionlessDetection =
"assets/icons/motionless_detection.svg";
static const String motionlessDetection = "assets/icons/motionless_detection.svg";
static const String acHeating = "assets/icons/ac_heating.svg";
static const String acPowerOff = "assets/icons/ac_power_off.svg";
static const String acFanMiddle = "assets/icons/ac_fan_middle.svg";
@ -72,22 +70,19 @@ class Assets {
"assets/icons/automation_functions/temp_password_unlock.svg";
static const String doorlockNormalOpen =
"assets/icons/automation_functions/doorlock_normal_open.svg";
static const String doorbell =
"assets/icons/automation_functions/doorbell.svg";
static const String doorbell = "assets/icons/automation_functions/doorbell.svg";
static const String remoteUnlockViaApp =
"assets/icons/automation_functions/remote_unlock_via_app.svg";
static const String doubleLock =
"assets/icons/automation_functions/double_lock.svg";
static const String selfTestResult =
"assets/icons/automation_functions/self_test_result.svg";
static const String lockAlarm =
"assets/icons/automation_functions/lock_alarm.svg";
static const String lockAlarm = "assets/icons/automation_functions/lock_alarm.svg";
static const String presenceState =
"assets/icons/automation_functions/presence_state.svg";
static const String currentTemp =
"assets/icons/automation_functions/current_temp.svg";
static const String presence =
"assets/icons/automation_functions/presence.svg";
static const String presence = "assets/icons/automation_functions/presence.svg";
static const String residualElectricity =
"assets/icons/automation_functions/residual_electricity.svg";
static const String hijackAlarm =
@ -104,15 +99,12 @@ class Assets {
// Presence Sensor Assets
static const String sensorMotionIcon = "assets/icons/sensor_motion_ic.svg";
static const String sensorPresenceIcon =
"assets/icons/sensor_presence_ic.svg";
static const String sensorPresenceIcon = "assets/icons/sensor_presence_ic.svg";
static const String sensorVacantIcon = "assets/icons/sensor_vacant_ic.svg";
static const String illuminanceRecordIcon =
"assets/icons/illuminance_record_ic.svg";
static const String presenceRecordIcon =
"assets/icons/presence_record_ic.svg";
static const String helpDescriptionIcon =
"assets/icons/help_description_ic.svg";
static const String presenceRecordIcon = "assets/icons/presence_record_ic.svg";
static const String helpDescriptionIcon = "assets/icons/help_description_ic.svg";
static const String lightPulp = "assets/icons/light_pulb.svg";
static const String acDevice = "assets/icons/ac_device.svg";
@ -162,12 +154,10 @@ class Assets {
static const String unit = 'assets/icons/unit_icon.svg';
static const String villa = 'assets/icons/villa_icon.svg';
static const String iconEdit = 'assets/icons/icon_edit_icon.svg';
static const String textFieldSearch =
'assets/icons/textfield_search_icon.svg';
static const String textFieldSearch = 'assets/icons/textfield_search_icon.svg';
static const String roundedAddIcon = 'assets/icons/rounded_add_icon.svg';
static const String addIcon = 'assets/icons/add_icon.svg';
static const String smartThermostatIcon =
'assets/icons/smart_thermostat_icon.svg';
static const String smartThermostatIcon = 'assets/icons/smart_thermostat_icon.svg';
static const String smartLightIcon = 'assets/icons/smart_light_icon.svg';
static const String presenceSensor = 'assets/icons/presence_sensor.svg';
static const String Gang3SwitchIcon = 'assets/icons/3_Gang_switch_icon.svg';
@ -215,8 +205,7 @@ class Assets {
//assets/icons/water_leak_normal.svg
static const String waterLeakNormal = 'assets/icons/water_leak_normal.svg';
//assets/icons/water_leak_detected.svg
static const String waterLeakDetected =
'assets/icons/water_leak_detected.svg';
static const String waterLeakDetected = 'assets/icons/water_leak_detected.svg';
//assets/icons/automation_records.svg
static const String automationRecords = 'assets/icons/automation_records.svg';
@ -287,16 +276,13 @@ class Assets {
"assets/icons/functions_icons/sensitivity.svg";
static const String assetsSensitivityOperationIcon =
"assets/icons/functions_icons/sesitivity_operation_icon.svg";
static const String assetsAcPower =
"assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPower = "assets/icons/functions_icons/ac_power.svg";
static const String assetsAcPowerOFF =
"assets/icons/functions_icons/ac_power_off.svg";
static const String assetsChildLock =
"assets/icons/functions_icons/child_lock.svg";
static const String assetsFreezing =
"assets/icons/functions_icons/freezing.svg";
static const String assetsFanSpeed =
"assets/icons/functions_icons/fan_speed.svg";
static const String assetsFreezing = "assets/icons/functions_icons/freezing.svg";
static const String assetsFanSpeed = "assets/icons/functions_icons/fan_speed.svg";
static const String assetsAcCooling =
"assets/icons/functions_icons/ac_cooling.svg";
static const String assetsAcHeating =
@ -305,8 +291,7 @@ class Assets {
"assets/icons/functions_icons/celsius_degrees.svg";
static const String assetsTempreture =
"assets/icons/functions_icons/tempreture.svg";
static const String assetsAcFanLow =
"assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanLow = "assets/icons/functions_icons/ac_fan_low.svg";
static const String assetsAcFanMiddle =
"assets/icons/functions_icons/ac_fan_middle.svg";
static const String assetsAcFanHigh =
@ -325,8 +310,7 @@ class Assets {
"assets/icons/functions_icons/far_detection.svg";
static const String assetsFarDetectionFunction =
"assets/icons/functions_icons/far_detection_function.svg";
static const String assetsIndicator =
"assets/icons/functions_icons/indicator.svg";
static const String assetsIndicator = "assets/icons/functions_icons/indicator.svg";
static const String assetsMotionDetection =
"assets/icons/functions_icons/motion_detection.svg";
static const String assetsMotionlessDetection =
@ -339,8 +323,7 @@ class Assets {
"assets/icons/functions_icons/master_state.svg";
static const String assetsSwitchAlarmSound =
"assets/icons/functions_icons/switch_alarm_sound.svg";
static const String assetsResetOff =
"assets/icons/functions_icons/reset_off.svg";
static const String assetsResetOff = "assets/icons/functions_icons/reset_off.svg";
// Assets for automation_functions
static const String assetsCardUnlock =
@ -384,14 +367,12 @@ class Assets {
static const String activeUser = 'assets/icons/active_user.svg';
static const String deActiveUser = 'assets/icons/deactive_user.svg';
static const String invitedIcon = 'assets/icons/invited_icon.svg';
static const String rectangleCheckBox =
'assets/icons/rectangle_check_box.png';
static const String rectangleCheckBox = 'assets/icons/rectangle_check_box.png';
static const String CheckBoxChecked = 'assets/icons/box_checked.png';
static const String emptyBox = 'assets/icons/empty_box.png';
static const String completeProcessIcon =
'assets/icons/compleate_process_icon.svg';
static const String currentProcessIcon =
'assets/icons/current_process_icon.svg';
static const String currentProcessIcon = 'assets/icons/current_process_icon.svg';
static const String uncomplete_ProcessIcon =
'assets/icons/uncompleate_process_icon.svg';
static const String wrongProcessIcon = 'assets/icons/wrong_process_icon.svg';
@ -412,11 +393,9 @@ class Assets {
static const String successIcon = 'assets/icons/success_icon.svg';
static const String spaceLocationIcon = 'assets/icons/spaseLocationIcon.svg';
static const String scenesPlayIcon = 'assets/icons/scenesPlayIcon.png';
static const String scenesPlayIconCheck =
'assets/icons/scenesPlayIconCheck.png';
static const String scenesPlayIconCheck = 'assets/icons/scenesPlayIconCheck.png';
static const String presenceStateIcon = 'assets/icons/presence_state.svg';
static const String currentDistanceIcon =
'assets/icons/current_distance_icon.svg';
static const String currentDistanceIcon = 'assets/icons/current_distance_icon.svg';
static const String farDetectionIcon = 'assets/icons/far_detection_icon.svg';
static const String motionDetectionSensitivityIcon =
@ -439,44 +418,29 @@ class Assets {
static const String cpsMode4 = 'assets/icons/cps_mode4.svg';
static const String closeToMotion = 'assets/icons/close_to_motion.svg';
static const String farAwayMotion = 'assets/icons/far_away_motion.svg';
static const String communicationFault =
'assets/icons/communication_fault.svg';
static const String communicationFault = 'assets/icons/communication_fault.svg';
static const String radarFault = 'assets/icons/radar_fault.svg';
static const String selfTestingSuccess =
'assets/icons/self_testing_success.svg';
static const String selfTestingFailure =
'assets/icons/self_testing_failure.svg';
static const String selfTestingTimeout =
'assets/icons/self_testing_timeout.svg';
static const String selfTestingSuccess = 'assets/icons/self_testing_success.svg';
static const String selfTestingFailure = 'assets/icons/self_testing_failure.svg';
static const String selfTestingTimeout = 'assets/icons/self_testing_timeout.svg';
static const String movingSpeed = 'assets/icons/moving_speed.svg';
static const String boundary = 'assets/icons/boundary.svg';
static const String motionMeter = 'assets/icons/motion_meter.svg';
static const String spatialStaticValue =
'assets/icons/spatial_static_value.svg';
static const String spatialMotionValue =
'assets/icons/spatial_motion_value.svg';
static const String spatialStaticValue = 'assets/icons/spatial_static_value.svg';
static const String spatialMotionValue = 'assets/icons/spatial_motion_value.svg';
static const String presenceJudgementThrshold =
'assets/icons/presence_judgement_threshold.svg';
static const String spaceType = 'assets/icons/space_type.svg';
static const String sportsPara = 'assets/icons/sports_para.svg';
static const String sensitivityFeature1 =
'assets/icons/sensitivity_feature_1.svg';
static const String sensitivityFeature2 =
'assets/icons/sensitivity_feature_2.svg';
static const String sensitivityFeature3 =
'assets/icons/sensitivity_feature_3.svg';
static const String sensitivityFeature4 =
'assets/icons/sensitivity_feature_4.svg';
static const String sensitivityFeature5 =
'assets/icons/sensitivity_feature_5.svg';
static const String sensitivityFeature6 =
'assets/icons/sensitivity_feature_6.svg';
static const String sensitivityFeature7 =
'assets/icons/sensitivity_feature_7.svg';
static const String sensitivityFeature8 =
'assets/icons/sensitivity_feature_8.svg';
static const String sensitivityFeature9 =
'assets/icons/sensitivity_feature_9.svg';
static const String sensitivityFeature1 = 'assets/icons/sensitivity_feature_1.svg';
static const String sensitivityFeature2 = 'assets/icons/sensitivity_feature_2.svg';
static const String sensitivityFeature3 = 'assets/icons/sensitivity_feature_3.svg';
static const String sensitivityFeature4 = 'assets/icons/sensitivity_feature_4.svg';
static const String sensitivityFeature5 = 'assets/icons/sensitivity_feature_5.svg';
static const String sensitivityFeature6 = 'assets/icons/sensitivity_feature_6.svg';
static const String sensitivityFeature7 = 'assets/icons/sensitivity_feature_7.svg';
static const String sensitivityFeature8 = 'assets/icons/sensitivity_feature_8.svg';
static const String sensitivityFeature9 = 'assets/icons/sensitivity_feature_9.svg';
static const String deviceTagIcon = 'assets/icons/device_tag_ic.svg';
static const String targetConfirmTimeIcon =
'assets/icons/target_confirm_time_icon.svg';

View File

@ -61,6 +61,7 @@ dependencies:
firebase_crashlytics: ^4.3.2
firebase_database: ^11.3.2
bloc: ^9.0.0
geocoding: ^4.0.0
gauge_indicator: ^0.4.3