From 511acc186fd500c304e0d86ed27f5dc2c4bba5c7 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 15:39:44 +0300 Subject: [PATCH 01/19] Created a param class for loading device location data. --- .../params/get_device_location_data_param.dart | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 lib/pages/analytics/params/get_device_location_data_param.dart diff --git a/lib/pages/analytics/params/get_device_location_data_param.dart b/lib/pages/analytics/params/get_device_location_data_param.dart new file mode 100644 index 00000000..c66af4d8 --- /dev/null +++ b/lib/pages/analytics/params/get_device_location_data_param.dart @@ -0,0 +1,16 @@ +class GetDeviceLocationDataParam { + const GetDeviceLocationDataParam({ + required this.latitude, + required this.longitude, + }); + + final double latitude; + final double longitude; + + Map toJson() { + return { + 'latitude': latitude, + 'longitude': longitude, + }; + } +} From e7476a084d866fb546c3ebe52b92a575c663bbae Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 15:39:51 +0300 Subject: [PATCH 02/19] Created a model class for loading device location data. --- .../models/device_location_info.dart | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 lib/pages/analytics/models/device_location_info.dart diff --git a/lib/pages/analytics/models/device_location_info.dart b/lib/pages/analytics/models/device_location_info.dart new file mode 100644 index 00000000..9b0095f7 --- /dev/null +++ b/lib/pages/analytics/models/device_location_info.dart @@ -0,0 +1,58 @@ +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 json) { + return DeviceLocationInfo( + airQuality: json['airQuality'] as double?, + humidity: json['humidity'] as double?, + city: json['city'] as String?, + country: json['country'] as String?, + address: json['address'] as String?, + 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 get props => [ + airQuality, + humidity, + city, + country, + address, + temperature, + ]; +} From 6ffb677c33edf1897395577d4073f16732e92843 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 15:40:15 +0300 Subject: [PATCH 03/19] Created an interface and its fake implementation for loading device location data. --- .../device_location_service.dart | 6 +++++ .../fake_device_location_service.dart | 22 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 lib/pages/analytics/services/device_location/device_location_service.dart create mode 100644 lib/pages/analytics/services/device_location/fake_device_location_service.dart diff --git a/lib/pages/analytics/services/device_location/device_location_service.dart b/lib/pages/analytics/services/device_location/device_location_service.dart new file mode 100644 index 00000000..d28b4a7b --- /dev/null +++ b/lib/pages/analytics/services/device_location/device_location_service.dart @@ -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 get(GetDeviceLocationDataParam param); +} diff --git a/lib/pages/analytics/services/device_location/fake_device_location_service.dart b/lib/pages/analytics/services/device_location/fake_device_location_service.dart new file mode 100644 index 00000000..c1a4e82f --- /dev/null +++ b/lib/pages/analytics/services/device_location/fake_device_location_service.dart @@ -0,0 +1,22 @@ +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 FakeDeviceLocationService implements DeviceLocationService { + const FakeDeviceLocationService(); + + @override + Future get(GetDeviceLocationDataParam param) async { + return await Future.delayed( + const Duration(milliseconds: 500), + () => const DeviceLocationInfo( + airQuality: 45.0, + humidity: 65.0, + city: 'Dubai', + country: 'UAE', + address: 'Business Bay', + temperature: 22.5, + ), + ); + } +} From d92b699a2b01d2a7e305e7e04352ad7ea4a9ce94 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 15:40:44 +0300 Subject: [PATCH 04/19] Created a bloc for loading and managing the state of device location data. --- .../device_location/device_location_bloc.dart | 50 +++++++++++++++++++ .../device_location_event.dart | 21 ++++++++ .../device_location_state.dart | 18 +++++++ 3 files changed, 89 insertions(+) create mode 100644 lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart create mode 100644 lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_event.dart create mode 100644 lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_state.dart diff --git a/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart new file mode 100644 index 00000000..4f41eb0c --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_bloc.dart @@ -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 { + DeviceLocationBloc( + this._deviceLocationService, + ) : super(const DeviceLocationState()) { + on(_onLoadDeviceLocation); + on(_onClearDeviceLocation); + } + + final DeviceLocationService _deviceLocationService; + + Future _onLoadDeviceLocation( + LoadDeviceLocationEvent event, + Emitter 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 emit, + ) { + emit(const DeviceLocationState()); + } +} diff --git a/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_event.dart b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_event.dart new file mode 100644 index 00000000..376d055b --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_event.dart @@ -0,0 +1,21 @@ +part of 'device_location_bloc.dart'; + +sealed class DeviceLocationEvent extends Equatable { + const DeviceLocationEvent(); + + @override + List get props => []; +} + +class LoadDeviceLocationEvent extends DeviceLocationEvent { + const LoadDeviceLocationEvent(this.param); + + final GetDeviceLocationDataParam param; + + @override + List get props => [param]; +} + +class ClearDeviceLocationEvent extends DeviceLocationEvent { + const ClearDeviceLocationEvent(); +} diff --git a/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_state.dart b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_state.dart new file mode 100644 index 00000000..15c681b6 --- /dev/null +++ b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_state.dart @@ -0,0 +1,18 @@ +part of 'device_location_bloc.dart'; + +enum DeviceLocationStatus { initial, loading, success, failure } + +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 get props => [status, locationInfo, errorMessage]; +} From 8ad048e18d95330165b06dfaf136096c82e00817 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 15:47:24 +0300 Subject: [PATCH 05/19] Added `geocoding: ^4.0.0` package. --- pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/pubspec.yaml b/pubspec.yaml index 7decc506..612477fc 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 + geocoding: ^4.0.0 dev_dependencies: From b6879035f069467efd24b7ab7069e9e12c9abdc9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Thu, 29 May 2025 15:47:34 +0300 Subject: [PATCH 06/19] Implemented geocoding functionality to retrieve and manage device location data using the newly added `geocoding` package. --- ...ode_device_location_service_decorator.dart | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 lib/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.dart diff --git a/lib/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.dart b/lib/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.dart new file mode 100644 index 00000000..a3ac1e55 --- /dev/null +++ b/lib/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.dart @@ -0,0 +1,40 @@ +import 'package:geocoding/geocoding.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 ReverseGeocodeDeviceLocationServiceDecorator implements DeviceLocationService { + const ReverseGeocodeDeviceLocationServiceDecorator(this._decoratee); + + final DeviceLocationService _decoratee; + + @override + Future get(GetDeviceLocationDataParam param) async { + try { + final deviceLocationInfo = await _decoratee.get(param); + + final placemarks = await placemarkFromCoordinates( + param.latitude, + param.longitude, + ); + + if (placemarks.isNotEmpty) { + final place = placemarks.first; + + final city = place.locality; + final country = place.country; + final address = place.street; + + return deviceLocationInfo.copyWith( + city: city, + country: country, + address: address, + ); + } + + return deviceLocationInfo; + } catch (e) { + throw Exception('Failed to reverse load device location info'); + } + } +} From 5654d66b600555798dc6d7986a80bee4b6096b1d Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 1 Jun 2025 09:51:01 +0300 Subject: [PATCH 07/19] Created a remote implementation for `DeviceLocationService`. --- .env.development | 3 +- .env.production | 3 +- .env.staging | 3 +- .../remote_device_location_service.dart | 88 +++++++++++++++++++ 4 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 lib/pages/analytics/services/device_location/remote_device_location_service.dart diff --git a/.env.development b/.env.development index e77609dc..8b8c7587 100644 --- a/.env.development +++ b/.env.development @@ -1,2 +1,3 @@ ENV_NAME=development -BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file +BASE_URL=https://syncrow-dev.azurewebsites.net +OPEN_WEATHER_API_KEY=5253339f3f994603cd406b0817823d02 diff --git a/.env.production b/.env.production index 4e9dcb81..73a13524 100644 --- a/.env.production +++ b/.env.production @@ -1,2 +1,3 @@ ENV_NAME=production -BASE_URL=https://syncrow-staging.azurewebsites.net \ No newline at end of file +BASE_URL=https://syncrow-staging.azurewebsites.net +OPEN_WEATHER_API_KEY=5253339f3f994603cd406b0817823d02 \ No newline at end of file diff --git a/.env.staging b/.env.staging index 9565b426..8ab31d93 100644 --- a/.env.staging +++ b/.env.staging @@ -1,2 +1,3 @@ ENV_NAME=staging -BASE_URL=https://syncrow-staging.azurewebsites.net \ No newline at end of file +BASE_URL=https://syncrow-staging.azurewebsites.net +OPEN_WEATHER_API_KEY=5253339f3f994603cd406b0817823d02 \ No newline at end of file diff --git a/lib/pages/analytics/services/device_location/remote_device_location_service.dart b/lib/pages/analytics/services/device_location/remote_device_location_service.dart new file mode 100644 index 00000000..b78be6cc --- /dev/null +++ b/lib/pages/analytics/services/device_location/remote_device_location_service.dart @@ -0,0 +1,88 @@ +import 'package:dio/dio.dart'; +import 'package:flutter_dotenv/flutter_dotenv.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 RemoteDeviceLocationService implements DeviceLocationService { + const RemoteDeviceLocationService(this._dio); + + final Dio _dio; + static final _openWeatherApiKey = dotenv.env['OPEN_WEATHER_API_KEY']!; + @override + Future get(GetDeviceLocationDataParam param) async { + try { + final results = await Future.wait([ + _getAirQualityData(param), + _getWeatherData(param), + ]); + + final airQuality = results[0] as double?; + final weatherData = results[1] as _WeatherData?; + + return DeviceLocationInfo( + airQuality: airQuality, + temperature: weatherData?.temperature, + humidity: weatherData?.humidity, + ); + } catch (e) { + throw Exception('Failed to fetch location data: $e'); + } + } + + Future _getAirQualityData(GetDeviceLocationDataParam param) async { + final response = await _dio.get( + 'https://api.openweathermap.org/data/2.5/air_pollution', + queryParameters: { + 'lat': param.latitude, + 'lon': param.longitude, + 'appid': _openWeatherApiKey, + }, + ); + + final data = response.data as Map; + final list = data['list'] as List; + if (list.isEmpty) return null; + + final main = list[0]['main'] as Map; + return (main['aqi'] as num).toDouble(); + } + + Future<_WeatherData?> _getWeatherData(GetDeviceLocationDataParam param) async { + final now = DateTime.now(); + final start = DateTime(now.year, now.month, now.day); + final end = DateTime(now.year, now.month, now.day, 23, 59, 59); + try { + final response = await _dio.get( + 'https://api.openweathermap.org/data/2.5/weather', + queryParameters: { + 'lat': param.latitude, + 'lon': param.longitude, + 'start': start.millisecondsSinceEpoch, + 'end': end.millisecondsSinceEpoch, + 'appid': _openWeatherApiKey, + }, + ); + + final data = response.data as Map; + final main = data['main'] as Map; + + return _WeatherData( + temperature: (main['temp'] as num).toDouble(), + humidity: (main['humidity'] as num).toDouble(), + ); + } catch (e) { + return null; + } + } +} + +class _WeatherData { + const _WeatherData({ + required this.temperature, + required this.humidity, + }); + + final double temperature; + final double humidity; +} From 2c4da63266ff35855c490e5a0850c584ca6aa1d5 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 1 Jun 2025 10:50:51 +0300 Subject: [PATCH 08/19] Injected `DeviceLocationBloc` into `AnalyticsPage`. --- .../analytics/views/analytics_page.dart | 10 ++++++++++ .../remote_device_location_service.dart | 19 ++++++++++++++----- 2 files changed, 24 insertions(+), 5 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index 68a531c8..ca07c389 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -1,5 +1,7 @@ +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/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'; @@ -16,6 +18,7 @@ import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_he 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/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'; @@ -101,6 +104,13 @@ class _AnalyticsPageState extends State { FakeRangeOfAqiService(), ), ), + BlocProvider( + create: (context) => DeviceLocationBloc( + RemoteDeviceLocationService( + Dio(BaseOptions(baseUrl: 'https://api.openweathermap.org/data/2.5')), + ), + ), + ), ], child: const AnalyticsPageForm(), ); diff --git a/lib/pages/analytics/services/device_location/remote_device_location_service.dart b/lib/pages/analytics/services/device_location/remote_device_location_service.dart index b78be6cc..707d6c61 100644 --- a/lib/pages/analytics/services/device_location/remote_device_location_service.dart +++ b/lib/pages/analytics/services/device_location/remote_device_location_service.dart @@ -9,6 +9,7 @@ class RemoteDeviceLocationService implements DeviceLocationService { final Dio _dio; static final _openWeatherApiKey = dotenv.env['OPEN_WEATHER_API_KEY']!; + @override Future get(GetDeviceLocationDataParam param) async { try { @@ -31,8 +32,12 @@ class RemoteDeviceLocationService implements DeviceLocationService { } Future _getAirQualityData(GetDeviceLocationDataParam param) async { - final response = await _dio.get( - 'https://api.openweathermap.org/data/2.5/air_pollution', + final response = await _dio.get>( + '/air_pollution/history', + options: Options( + method: 'GET', + responseType: ResponseType.json, + ), queryParameters: { 'lat': param.latitude, 'lon': param.longitude, @@ -40,7 +45,7 @@ class RemoteDeviceLocationService implements DeviceLocationService { }, ); - final data = response.data as Map; + final data = response.data ?? {}; final list = data['list'] as List; if (list.isEmpty) return null; @@ -53,8 +58,12 @@ class RemoteDeviceLocationService implements DeviceLocationService { final start = DateTime(now.year, now.month, now.day); final end = DateTime(now.year, now.month, now.day, 23, 59, 59); try { - final response = await _dio.get( - 'https://api.openweathermap.org/data/2.5/weather', + final response = await _dio.get>( + '/weather', + options: Options( + method: 'GET', + responseType: ResponseType.json, + ), queryParameters: { 'lat': param.latitude, 'lon': param.longitude, From 3d183528c56c55daa12bf506caea1fd4ffa5e00b Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 1 Jun 2025 10:57:49 +0300 Subject: [PATCH 09/19] Fixed thrown exceptions because of `Expanded` widgets. --- .../air_quality/widgets/aqi_device_info.dart | 97 +++++++++---------- .../widgets/aqi_location_info.dart | 36 +++---- 2 files changed, 66 insertions(+), 67 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart index f3773c29..ebe88614 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_device_info.dart @@ -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, + ), + ], ), ); }, 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 index f8e087b8..8426328e 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart @@ -18,24 +18,24 @@ class AqiLocationInfo extends StatelessWidget { 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, - ), - ], + 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, + ), + ], ), ), ], From b0ed84489370ecaaaf7000d8c4e5fb0641e4e2e2 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Sun, 1 Jun 2025 14:37:22 +0300 Subject: [PATCH 10/19] made events and state class `final`s, to better document that they shouldn't be extended. --- .../blocs/device_location/device_location_event.dart | 4 ++-- .../blocs/device_location/device_location_state.dart | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_event.dart b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_event.dart index 376d055b..37137e4a 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_event.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_event.dart @@ -7,7 +7,7 @@ sealed class DeviceLocationEvent extends Equatable { List get props => []; } -class LoadDeviceLocationEvent extends DeviceLocationEvent { +final class LoadDeviceLocationEvent extends DeviceLocationEvent { const LoadDeviceLocationEvent(this.param); final GetDeviceLocationDataParam param; @@ -16,6 +16,6 @@ class LoadDeviceLocationEvent extends DeviceLocationEvent { List get props => [param]; } -class ClearDeviceLocationEvent extends DeviceLocationEvent { +final class ClearDeviceLocationEvent extends DeviceLocationEvent { const ClearDeviceLocationEvent(); } diff --git a/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_state.dart b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_state.dart index 15c681b6..8f66ad28 100644 --- a/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_state.dart +++ b/lib/pages/analytics/modules/air_quality/blocs/device_location/device_location_state.dart @@ -2,7 +2,7 @@ part of 'device_location_bloc.dart'; enum DeviceLocationStatus { initial, loading, success, failure } -class DeviceLocationState extends Equatable { +final class DeviceLocationState extends Equatable { const DeviceLocationState({ this.status = DeviceLocationStatus.initial, this.locationInfo, From bcb6e49a015a695db5411b4593286aca68e3e461 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 09:17:38 +0300 Subject: [PATCH 11/19] Deleted `FakeDeviceLocationService` class, since it is no longer needed. --- .../fake_device_location_service.dart | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 lib/pages/analytics/services/device_location/fake_device_location_service.dart diff --git a/lib/pages/analytics/services/device_location/fake_device_location_service.dart b/lib/pages/analytics/services/device_location/fake_device_location_service.dart deleted file mode 100644 index c1a4e82f..00000000 --- a/lib/pages/analytics/services/device_location/fake_device_location_service.dart +++ /dev/null @@ -1,22 +0,0 @@ -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 FakeDeviceLocationService implements DeviceLocationService { - const FakeDeviceLocationService(); - - @override - Future get(GetDeviceLocationDataParam param) async { - return await Future.delayed( - const Duration(milliseconds: 500), - () => const DeviceLocationInfo( - airQuality: 45.0, - humidity: 65.0, - city: 'Dubai', - country: 'UAE', - address: 'Business Bay', - temperature: 22.5, - ), - ); - } -} From 8d999f118c458878ae37d398c414106b289a94e0 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 09:18:28 +0300 Subject: [PATCH 12/19] Connected `RemoteDeviceLocationService` to the new BE API, instead of directly fetching the data from OpenWeather Api's. --- .../remote_device_location_service.dart | 97 +++---------------- 1 file changed, 16 insertions(+), 81 deletions(-) diff --git a/lib/pages/analytics/services/device_location/remote_device_location_service.dart b/lib/pages/analytics/services/device_location/remote_device_location_service.dart index 707d6c61..dce547a2 100644 --- a/lib/pages/analytics/services/device_location/remote_device_location_service.dart +++ b/lib/pages/analytics/services/device_location/remote_device_location_service.dart @@ -1,97 +1,32 @@ import 'package:dio/dio.dart'; -import 'package:flutter_dotenv/flutter_dotenv.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._dio); + const RemoteDeviceLocationService(this._httpService); - final Dio _dio; - static final _openWeatherApiKey = dotenv.env['OPEN_WEATHER_API_KEY']!; + final HTTPService _httpService; + + static const _defaultErrorMessage = 'Failed to load device location'; @override Future get(GetDeviceLocationDataParam param) async { try { - final results = await Future.wait([ - _getAirQualityData(param), - _getWeatherData(param), - ]); - - final airQuality = results[0] as double?; - final weatherData = results[1] as _WeatherData?; - - return DeviceLocationInfo( - airQuality: airQuality, - temperature: weatherData?.temperature, - humidity: weatherData?.humidity, + final response = await _httpService.get( + path: '/weather', + queryParameters: param.toJson(), + expectedResponseModel: (data) => DeviceLocationInfo.fromJson(data), ); + return response; + } on DioException catch (e) { + final message = e.response?.data as Map?; + final error = message?['error'] as Map?; + final errorMessage = error?['error'] as String? ?? ''; + throw Exception(errorMessage); } catch (e) { - throw Exception('Failed to fetch location data: $e'); - } - } - - Future _getAirQualityData(GetDeviceLocationDataParam param) async { - final response = await _dio.get>( - '/air_pollution/history', - options: Options( - method: 'GET', - responseType: ResponseType.json, - ), - queryParameters: { - 'lat': param.latitude, - 'lon': param.longitude, - 'appid': _openWeatherApiKey, - }, - ); - - final data = response.data ?? {}; - final list = data['list'] as List; - if (list.isEmpty) return null; - - final main = list[0]['main'] as Map; - return (main['aqi'] as num).toDouble(); - } - - Future<_WeatherData?> _getWeatherData(GetDeviceLocationDataParam param) async { - final now = DateTime.now(); - final start = DateTime(now.year, now.month, now.day); - final end = DateTime(now.year, now.month, now.day, 23, 59, 59); - try { - final response = await _dio.get>( - '/weather', - options: Options( - method: 'GET', - responseType: ResponseType.json, - ), - queryParameters: { - 'lat': param.latitude, - 'lon': param.longitude, - 'start': start.millisecondsSinceEpoch, - 'end': end.millisecondsSinceEpoch, - 'appid': _openWeatherApiKey, - }, - ); - - final data = response.data as Map; - final main = data['main'] as Map; - - return _WeatherData( - temperature: (main['temp'] as num).toDouble(), - humidity: (main['humidity'] as num).toDouble(), - ); - } catch (e) { - return null; + throw Exception('$_defaultErrorMessage: $e'); } } } - -class _WeatherData { - const _WeatherData({ - required this.temperature, - required this.humidity, - }); - - final double temperature; - final double humidity; -} From e48fc8b82c7bf130f49899e54d23506b7056bf37 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 09:27:21 +0300 Subject: [PATCH 13/19] loads and clears `DeviceLocationBloc`. --- .../helpers/fetch_air_quality_data_helper.dart | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart index 65e62365..aaffc3fd 100644 --- a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart +++ b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart @@ -1,11 +1,13 @@ 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/blocs/range_of_aqi/range_of_aqi_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/air_quality/widgets/aqi_type_dropdown.dart'; import 'package:syncrow_web/pages/analytics/modules/analytics/blocs/analytics_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_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 { @@ -39,6 +41,8 @@ abstract final class FetchAirQualityDataHelper { ); context.read().add(const ClearRangeOfAqiEvent()); + + context.read().add(const ClearDeviceLocationEvent()); } static void loadAnalyticsDevices( @@ -58,6 +62,15 @@ abstract final class FetchAirQualityDataHelper { context.read() ..add(const RealtimeDeviceChangesClosed()) ..add(RealtimeDeviceChangesStarted(device.uuid)); + + context.read().add( + const LoadDeviceLocationEvent( + GetDeviceLocationDataParam( + latitude: 35.6895, + longitude: 139.6917, + ), + ), + ); }, ), ); From 25a55ad82033ff6c1787bfe4c83c88478a1213b4 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 09:27:46 +0300 Subject: [PATCH 14/19] made `GetDeviceLocationDataParam.toJson` method have the correct keys for the API. --- .../analytics/params/get_device_location_data_param.dart | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/lib/pages/analytics/params/get_device_location_data_param.dart b/lib/pages/analytics/params/get_device_location_data_param.dart index c66af4d8..29427d10 100644 --- a/lib/pages/analytics/params/get_device_location_data_param.dart +++ b/lib/pages/analytics/params/get_device_location_data_param.dart @@ -7,10 +7,5 @@ class GetDeviceLocationDataParam { final double latitude; final double longitude; - Map toJson() { - return { - 'latitude': latitude, - 'longitude': longitude, - }; - } + Map toJson() => {'lat': latitude, 'lon': longitude}; } From 1edeb664aa95249021a61f8a13c7725db904467a Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 09:28:16 +0300 Subject: [PATCH 15/19] Connected data coming from `DeviceLocationBloc` into the respective widgets. --- .../air_quality/widgets/aqi_location.dart | 31 +++++++++- .../widgets/aqi_location_info.dart | 61 +++++++++++-------- 2 files changed, 65 insertions(+), 27 deletions(-) diff --git a/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart b/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart index 3f1d1f09..2503874f 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location.dart @@ -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 = []; + + 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, 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 index 8426328e..983f76b2 100644 --- a/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart +++ b/lib/pages/analytics/modules/air_quality/widgets/aqi_location_info.dart @@ -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( + return BlocBuilder( + builder: (context, state) { + final info = state.locationInfo; + return Container( + decoration: secondarySection.copyWith(boxShadow: const []), + padding: const EdgeInsetsDirectional.all(20), + child: Column( spacing: 8, children: [ - AqiLocationInfoCell( - label: 'Temperature', - value: ' 25°', - svgPath: Assets.aqiTemperature, + AqiLocation( + city: info?.city, + country: info?.country, + address: info?.address, ), - AqiLocationInfoCell( - label: 'Humidity', - value: '25%', - svgPath: Assets.aqiHumidity, - ), - AqiLocationInfoCell( - label: 'Air Quality', - value: ' 120', - svgPath: Assets.aqiAirQuality, + 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, + ), + ], + ), ), ], - ), ), - ], - ), + ); + }, ); } } From e2c44ba85fdeabbd62018ca716937d43bcf6a1dc Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 09:28:50 +0300 Subject: [PATCH 16/19] injected the remote and reverse geocoder dependenies into `DeviceLocationBloc`. --- .../analytics/modules/analytics/views/analytics_page.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index ca07c389..0a45ba8d 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -1,4 +1,3 @@ -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/device_location/device_location_bloc.dart'; @@ -19,6 +18,7 @@ import 'package:syncrow_web/pages/analytics/services/analytics_devices/analytics 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/remote_device_location_service.dart'; +import 'package:syncrow_web/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.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'; @@ -106,8 +106,8 @@ class _AnalyticsPageState extends State { ), BlocProvider( create: (context) => DeviceLocationBloc( - RemoteDeviceLocationService( - Dio(BaseOptions(baseUrl: 'https://api.openweathermap.org/data/2.5')), + ReverseGeocodeDeviceLocationServiceDecorator( + RemoteDeviceLocationService(_httpService), ), ), ), From 651ac6785eb0afdf3f054c1bf0943a1d819bc971 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 09:32:56 +0300 Subject: [PATCH 17/19] removed open weather api keys from `.env` files. --- .env.development | 3 +-- .env.production | 3 +-- .env.staging | 3 +-- 3 files changed, 3 insertions(+), 6 deletions(-) diff --git a/.env.development b/.env.development index 8b8c7587..e77609dc 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,2 @@ ENV_NAME=development -BASE_URL=https://syncrow-dev.azurewebsites.net -OPEN_WEATHER_API_KEY=5253339f3f994603cd406b0817823d02 +BASE_URL=https://syncrow-dev.azurewebsites.net \ No newline at end of file diff --git a/.env.production b/.env.production index 73a13524..4e9dcb81 100644 --- a/.env.production +++ b/.env.production @@ -1,3 +1,2 @@ ENV_NAME=production -BASE_URL=https://syncrow-staging.azurewebsites.net -OPEN_WEATHER_API_KEY=5253339f3f994603cd406b0817823d02 \ No newline at end of file +BASE_URL=https://syncrow-staging.azurewebsites.net \ No newline at end of file diff --git a/.env.staging b/.env.staging index 8ab31d93..9565b426 100644 --- a/.env.staging +++ b/.env.staging @@ -1,3 +1,2 @@ ENV_NAME=staging -BASE_URL=https://syncrow-staging.azurewebsites.net -OPEN_WEATHER_API_KEY=5253339f3f994603cd406b0817823d02 \ No newline at end of file +BASE_URL=https://syncrow-staging.azurewebsites.net \ No newline at end of file From 79b974ee6cbe953096d11852d775a8f2f9696fb9 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 09:36:41 +0300 Subject: [PATCH 18/19] re-injected `AirQualityDistributionBloc` into `AnalyticsPage`. --- .../analytics/modules/analytics/views/analytics_page.dart | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index 0a45ba8d..01dc8ef5 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:syncrow_web/pages/analytics/modules/air_quality/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'; @@ -14,6 +15,7 @@ import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/real import 'package:syncrow_web/pages/analytics/modules/energy_management/blocs/total_energy_consumption/total_energy_consumption_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy/occupancy_bloc.dart'; import 'package:syncrow_web/pages/analytics/modules/occupancy/blocs/occupancy_heat_map/occupancy_heat_map_bloc.dart'; +import 'package:syncrow_web/pages/analytics/services/air_quality_distribution/fake_air_quality_distribution_service.dart'; 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'; @@ -104,6 +106,11 @@ class _AnalyticsPageState extends State { FakeRangeOfAqiService(), ), ), + BlocProvider( + create: (context) => AirQualityDistributionBloc( + FakeAirQualityDistributionService(), + ), + ), BlocProvider( create: (context) => DeviceLocationBloc( ReverseGeocodeDeviceLocationServiceDecorator( From 24a7f3ac2a2f214eefaf32c1800591d7e8bfd557 Mon Sep 17 00:00:00 2001 From: Faris Armoush Date: Wed, 4 Jun 2025 13:06:27 +0300 Subject: [PATCH 19/19] SP-1594-device-location-api-integration. --- .../analytics/models/analytics_device.dart | 6 +++ .../models/device_location_info.dart | 5 +-- .../fetch_air_quality_data_helper.dart | 8 ++-- .../analytics/views/analytics_page.dart | 10 ++++- ...ce_location_details_service_decorator.dart | 40 +++++++++++++++++++ .../remote_device_location_service.dart | 7 +++- ...ode_device_location_service_decorator.dart | 40 ------------------- 7 files changed, 65 insertions(+), 51 deletions(-) create mode 100644 lib/pages/analytics/services/device_location/device_location_details_service_decorator.dart delete mode 100644 lib/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.dart diff --git a/lib/pages/analytics/models/analytics_device.dart b/lib/pages/analytics/models/analytics_device.dart index eaac8b2b..3340a41d 100644 --- a/lib/pages/analytics/models/analytics_device.dart +++ b/lib/pages/analytics/models/analytics_device.dart @@ -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 json) { return AnalyticsDevice( @@ -35,6 +39,8 @@ class AnalyticsDevice { ? ProductDevice.fromJson(json['productDevice'] as Map) : 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, ); } } diff --git a/lib/pages/analytics/models/device_location_info.dart b/lib/pages/analytics/models/device_location_info.dart index 9b0095f7..aef7eebb 100644 --- a/lib/pages/analytics/models/device_location_info.dart +++ b/lib/pages/analytics/models/device_location_info.dart @@ -19,11 +19,8 @@ class DeviceLocationInfo extends Equatable { factory DeviceLocationInfo.fromJson(Map json) { return DeviceLocationInfo( - airQuality: json['airQuality'] as double?, + airQuality: json['aqi'] as double?, humidity: json['humidity'] as double?, - city: json['city'] as String?, - country: json['country'] as String?, - address: json['address'] as String?, temperature: json['temperature'] as double?, ); } diff --git a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart index 7f26bb5a..cb37484c 100644 --- a/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart +++ b/lib/pages/analytics/modules/air_quality/helpers/fetch_air_quality_data_helper.dart @@ -65,7 +65,7 @@ abstract final class FetchAirQualityDataHelper { communityUuid: communityUuid, spaceUuid: spaceUuid, deviceTypes: ['AQI'], - requestType: AnalyticsDeviceRequestType.energyManagement, + requestType: AnalyticsDeviceRequestType.occupancy, ), onSuccess: (device) { context.read() @@ -73,10 +73,10 @@ abstract final class FetchAirQualityDataHelper { ..add(RealtimeDeviceChangesStarted(device.uuid)); context.read().add( - const LoadDeviceLocationEvent( + LoadDeviceLocationEvent( GetDeviceLocationDataParam( - latitude: 35.6895, - longitude: 139.6917, + latitude: device.latitude ?? 0, + longitude: device.longitude ?? 0, ), ), ); diff --git a/lib/pages/analytics/modules/analytics/views/analytics_page.dart b/lib/pages/analytics/modules/analytics/views/analytics_page.dart index 01dc8ef5..1ecd9aa3 100644 --- a/lib/pages/analytics/modules/analytics/views/analytics_page.dart +++ b/lib/pages/analytics/modules/analytics/views/analytics_page.dart @@ -1,3 +1,4 @@ +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'; @@ -19,8 +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/device_location/reverse_geocode_device_location_service_decorator.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'; @@ -113,8 +114,13 @@ class _AnalyticsPageState extends State { ), BlocProvider( create: (context) => DeviceLocationBloc( - ReverseGeocodeDeviceLocationServiceDecorator( + DeviceLocationDetailsServiceDecorator( RemoteDeviceLocationService(_httpService), + Dio( + BaseOptions( + baseUrl: 'https://nominatim.openstreetmap.org/', + ), + ), ), ), ), diff --git a/lib/pages/analytics/services/device_location/device_location_details_service_decorator.dart b/lib/pages/analytics/services/device_location/device_location_details_service_decorator.dart new file mode 100644 index 00000000..0239bcb7 --- /dev/null +++ b/lib/pages/analytics/services/device_location/device_location_details_service_decorator.dart @@ -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 get(GetDeviceLocationDataParam param) async { + try { + final deviceLocationInfo = await _decoratee.get(param); + final response = await _dio.get>( + 'reverse', + queryParameters: { + 'format': 'json', + 'lat': param.latitude, + 'lon': param.longitude, + }, + ); + + final data = response.data; + if (data != null) { + final addressData = data['address'] as Map; + 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()}'); + } + } +} diff --git a/lib/pages/analytics/services/device_location/remote_device_location_service.dart b/lib/pages/analytics/services/device_location/remote_device_location_service.dart index dce547a2..b8820180 100644 --- a/lib/pages/analytics/services/device_location/remote_device_location_service.dart +++ b/lib/pages/analytics/services/device_location/remote_device_location_service.dart @@ -17,7 +17,12 @@ class RemoteDeviceLocationService implements DeviceLocationService { final response = await _httpService.get( path: '/weather', queryParameters: param.toJson(), - expectedResponseModel: (data) => DeviceLocationInfo.fromJson(data), + expectedResponseModel: (data) { + final response = data as Map; + final location = response['data'] as Map; + + return DeviceLocationInfo.fromJson(location); + }, ); return response; } on DioException catch (e) { diff --git a/lib/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.dart b/lib/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.dart deleted file mode 100644 index a3ac1e55..00000000 --- a/lib/pages/analytics/services/device_location/reverse_geocode_device_location_service_decorator.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:geocoding/geocoding.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 ReverseGeocodeDeviceLocationServiceDecorator implements DeviceLocationService { - const ReverseGeocodeDeviceLocationServiceDecorator(this._decoratee); - - final DeviceLocationService _decoratee; - - @override - Future get(GetDeviceLocationDataParam param) async { - try { - final deviceLocationInfo = await _decoratee.get(param); - - final placemarks = await placemarkFromCoordinates( - param.latitude, - param.longitude, - ); - - if (placemarks.isNotEmpty) { - final place = placemarks.first; - - final city = place.locality; - final country = place.country; - final address = place.street; - - return deviceLocationInfo.copyWith( - city: city, - country: country, - address: address, - ); - } - - return deviceLocationInfo; - } catch (e) { - throw Exception('Failed to reverse load device location info'); - } - } -}