From d5ee3c957037378cc0f61bc3f67f7221de68911e Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Wed, 29 Jan 2025 12:58:22 +0300 Subject: [PATCH 1/5] Enhanced the search functionality to include both Google Places results and places from the AirQualityResponse --- mobile-v3/lib/src/app/map/pages/map_page.dart | 332 ++++++++++++------ 1 file changed, 215 insertions(+), 117 deletions(-) diff --git a/mobile-v3/lib/src/app/map/pages/map_page.dart b/mobile-v3/lib/src/app/map/pages/map_page.dart index 1e3175c95b..7d94cdf475 100644 --- a/mobile-v3/lib/src/app/map/pages/map_page.dart +++ b/mobile-v3/lib/src/app/map/pages/map_page.dart @@ -40,6 +40,30 @@ class _MapScreenState extends State String currentFilter = "All"; List allMeasurements = []; + List localSearchResults = []; + + List searchAirQualityLocations( + String query, List measurements) { + query = query.toLowerCase(); + return measurements.where((measurement) { + if (measurement.siteDetails != null) { + // Search through multiple location fields + return (measurement.siteDetails!.city?.toLowerCase().contains(query) ?? + false) || + (measurement.siteDetails!.locationName + ?.toLowerCase() + .contains(query) ?? + false) || + (measurement.siteDetails!.name?.toLowerCase().contains(query) ?? + false) || + (measurement.siteDetails!.town?.toLowerCase().contains(query) ?? + false) || + (measurement.siteDetails!.district?.toLowerCase().contains(query) ?? + false); + } + return false; + }).toList(); + } void filterByCountry(String country, List measurements) { setState(() { @@ -781,12 +805,23 @@ class _MapScreenState extends State controller: searchController, onChanged: (value) { print(value); - if (value == "") { + if (value.isEmpty) { googlePlacesBloc! .add(ResetGooglePlaces()); + setState(() { + // Reset local search results + localSearchResults = []; + }); + } else { + googlePlacesBloc! + .add(SearchPlace(value)); + setState(() { + localSearchResults = + searchAirQualityLocations( + value, + allMeasurements); + }); } - googlePlacesBloc! - .add(SearchPlace(value)); }, style: TextStyle(fontSize: 14), onTap: () => toggleModal(true), @@ -820,7 +855,11 @@ class _MapScreenState extends State clearGooglePlaces(), child: Icon( Icons.close, - color: Theme.of(context).textTheme.headlineLarge!.color, + color: Theme.of( + context) + .textTheme + .headlineLarge! + .color, )); } return SizedBox(); @@ -860,53 +899,138 @@ class _MapScreenState extends State ); } else if (placesState is SearchLoaded) { - if (placesState.response - .predictions.isEmpty) { - return Center( - child: Text( - "No results found", - style: TextStyle( - fontSize: 18, - fontWeight: - FontWeight.w500, - color: AppColors - .boldHeadlineColor), - ), - ); - } - return ListView.separated( - separatorBuilder: - (context, index) { - return Divider( - indent: 50, - ); - }, - padding: - const EdgeInsets.only(), - shrinkWrap: true, - itemCount: placesState - .response - .predictions - .length, - itemBuilder: - (context, index) { - Prediction prediction = - placesState.response - .predictions[index]; + return Column( + children: [ + // Show local AirQuality matches first + if (localSearchResults + .isNotEmpty) ...[ + Text( + "Air Quality Monitoring Locations", + style: TextStyle( + fontWeight: + FontWeight + .bold)), + ListView.separated( + shrinkWrap: true, + itemCount: + localSearchResults + .length, + separatorBuilder: + (context, index) => + Divider( + indent: 50), + itemBuilder: + (context, index) { + Measurement + measurement = + localSearchResults[ + index]; + return GestureDetector( + onTap: () => + viewDetails( + measurement: + measurement), + child: + LocationDisplayWidget( + title: measurement + .siteDetails! + .name ?? + "", + subTitle: measurement + .siteDetails! + .locationName ?? + "", + ), + ); + }), + Divider(), + ], - return GestureDetector( - onTap: () => viewDetails( - placeName: prediction - .description), - child: LocationDisplayWidget( - title: prediction - .description, - subTitle: prediction - .structuredFormatting - .mainText), - ); - }); + // Then show Google Places results + Text("Other Locations", + style: TextStyle( + fontWeight: + FontWeight.bold)), + ListView.separated( + shrinkWrap: true, + itemCount: placesState + .response + .predictions + .length, + separatorBuilder: + (context, index) => + Divider( + indent: 50), + itemBuilder: + (context, index) { + Prediction prediction = + placesState.response + .predictions[ + index]; + return GestureDetector( + onTap: () => viewDetails( + placeName: prediction + .description), + child: LocationDisplayWidget( + title: prediction + .description, + subTitle: prediction + .structuredFormatting + .mainText), + ); + }), + ], + ); } + // } else if (placesState + // is SearchLoaded) { + // if (placesState.response + // .predictions.isEmpty) { + // return Center( + // child: Text( + // "No results found", + // style: TextStyle( + // fontSize: 18, + // fontWeight: + // FontWeight.w500, + // color: AppColors + // .boldHeadlineColor), + // ), + // ); + // } + // return ListView.separated( + // separatorBuilder: + // (context, index) { + // return Divider( + // indent: 50, + // ); + // }, + // padding: + // const EdgeInsets.only(), + // shrinkWrap: true, + // itemCount: placesState + // .response + // .predictions + // .length, + // itemBuilder: + // (context, index) { + // Prediction prediction = + // placesState.response + // .predictions[index]; + + // return GestureDetector( + // onTap: () => viewDetails( + // placeName: prediction + // .description), + // child: LocationDisplayWidget( + // title: prediction + // .description, + // subTitle: prediction + // .structuredFormatting + // .mainText), + // ); + // }); + // } return Expanded( child: Column( crossAxisAlignment: @@ -1012,90 +1136,64 @@ class _MapScreenState extends State Expanded( child: Builder( - builder: (context) { - if (currentFilter == - "All") { - return ListView - .separated( - separatorBuilder: - (context, index) { - return Divider( - indent: 50, - ); - }, - padding: - const EdgeInsets - .only(), - shrinkWrap: true, - itemCount: 15, - itemBuilder: - (context, index) { - Measurement - measurement = - allMeasurements[ - index]; + builder: (context) { + List + measurements = + currentFilter == + "All" + ? allMeasurements + : filteredMeasurements; + + // If the list is empty, show a message instead of throwing an error + if (measurements + .isEmpty) { + return Center( + child: Text( + "No measurements available"), + ); + } - return GestureDetector( - onTap: () => - viewDetails( - measurement: - measurement), - child: - LocationDisplayWidget( - title: measurement - .siteDetails! - .city ?? - "", - subTitle: measurement - .siteDetails! - .locationName ?? - "", - )); - }, - ); - } else { return ListView .separated( separatorBuilder: - (context, index) { - return Divider( - indent: 50, - ); - }, + (context, + index) => + const Divider( + indent: + 50), padding: - const EdgeInsets - .only(), + EdgeInsets.zero, shrinkWrap: true, itemCount: - filteredMeasurements + measurements .length, itemBuilder: (context, index) { Measurement measurement = - filteredMeasurements[ + measurements[ index]; - return GestureDetector( - onTap: () => - viewDetails( - measurement: - measurement), - child: - LocationDisplayWidget( - title: measurement - .siteDetails! - .city ?? - "", - subTitle: measurement - .siteDetails! - .locationName ?? - "", - )); + onTap: () => + viewDetails( + measurement: + measurement), + child: + LocationDisplayWidget( + title: measurement + .siteDetails + ?.city ?? + "Unknown City", + subTitle: measurement + .siteDetails + ?.locationName ?? + "Unknown Location", + ), + ); }, ); - } - }), + }, + ), ) ], ), From 8da294bc41eefb2878bb9ae2a50e8bf6ee884cb8 Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Wed, 29 Jan 2025 14:06:17 +0300 Subject: [PATCH 2/5] Refactor analytics card and map page to improve site details display and enhance search functionality --- .../lib/src/app/dashboard/widgets/analytics_card.dart | 2 +- .../dashboard/widgets/analytics_forecast_widget.dart | 10 ++++------ mobile-v3/lib/src/app/map/pages/map_page.dart | 7 +++++-- 3 files changed, 10 insertions(+), 9 deletions(-) diff --git a/mobile-v3/lib/src/app/dashboard/widgets/analytics_card.dart b/mobile-v3/lib/src/app/dashboard/widgets/analytics_card.dart index 6ad860b629..31dff6608a 100644 --- a/mobile-v3/lib/src/app/dashboard/widgets/analytics_card.dart +++ b/mobile-v3/lib/src/app/dashboard/widgets/analytics_card.dart @@ -95,7 +95,7 @@ class AnalyticsCard extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text(measurement.siteDetails!.locationName ?? "", + Text(measurement.siteDetails!.name ?? "", style: TextStyle( fontSize: 28, fontWeight: FontWeight.w700, diff --git a/mobile-v3/lib/src/app/dashboard/widgets/analytics_forecast_widget.dart b/mobile-v3/lib/src/app/dashboard/widgets/analytics_forecast_widget.dart index d36eea1c07..0ff51b567f 100644 --- a/mobile-v3/lib/src/app/dashboard/widgets/analytics_forecast_widget.dart +++ b/mobile-v3/lib/src/app/dashboard/widgets/analytics_forecast_widget.dart @@ -27,16 +27,14 @@ class _AnalyticsForecastWidgetState extends State { double _getResponsiveHeight(BuildContext context) { final screenHeight = MediaQuery.of(context).size.height; - // Calculate height based on screen size, with minimum and maximum bounds - final height = screenHeight * 0.1; // 10% of screen height - return height.clamp(60.0, 100.0); // Min 60, max 100 + final height = screenHeight * 0.1; + return height.clamp(60.0, 100.0); } double _getResponsiveIconSize(BuildContext context) { final screenWidth = MediaQuery.of(context).size.width; - // Calculate icon size based on screen width - final iconSize = screenWidth * 0.04; // 4% of screen width - return iconSize.clamp(20.0, 30.0); // Min 20, max 30 + final iconSize = screenWidth * 0.04; + return iconSize.clamp(20.0, 30.0); } double _getResponsiveMargin(BuildContext context) { diff --git a/mobile-v3/lib/src/app/map/pages/map_page.dart b/mobile-v3/lib/src/app/map/pages/map_page.dart index 7d94cdf475..6c368b8e2e 100644 --- a/mobile-v3/lib/src/app/map/pages/map_page.dart +++ b/mobile-v3/lib/src/app/map/pages/map_page.dart @@ -56,6 +56,10 @@ class _MapScreenState extends State false) || (measurement.siteDetails!.name?.toLowerCase().contains(query) ?? false) || + (measurement.siteDetails!.searchName?.toLowerCase().contains(query) ?? + false) || + (measurement.siteDetails!.formattedName?.toLowerCase().contains(query) ?? + false) || (measurement.siteDetails!.town?.toLowerCase().contains(query) ?? false) || (measurement.siteDetails!.district?.toLowerCase().contains(query) ?? @@ -69,7 +73,6 @@ class _MapScreenState extends State setState(() { filteredMeasurements = measurements.where((measurement) { if (measurement.siteDetails != null) { - print(measurement.siteDetails!.country); return measurement.siteDetails!.country == country; } return false; @@ -1186,7 +1189,7 @@ class _MapScreenState extends State "Unknown City", subTitle: measurement .siteDetails - ?.locationName ?? + ?.name ?? "Unknown Location", ), ); From 87271581de7819011a7a43d8184cc78616dee523 Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Wed, 29 Jan 2025 14:34:09 +0300 Subject: [PATCH 3/5] Fix variable references in LocationDisplayWidget for improved clarity --- mobile-v3/lib/src/app/map/pages/map_page.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile-v3/lib/src/app/map/pages/map_page.dart b/mobile-v3/lib/src/app/map/pages/map_page.dart index 6c368b8e2e..f5b41cb7b8 100644 --- a/mobile-v3/lib/src/app/map/pages/map_page.dart +++ b/mobile-v3/lib/src/app/map/pages/map_page.dart @@ -937,11 +937,11 @@ class _MapScreenState extends State LocationDisplayWidget( title: measurement .siteDetails! - .name ?? + .locationName ?? "", subTitle: measurement .siteDetails! - .locationName ?? + .name ?? "", ), ); From 4787a92e9084d78db7b0ce1f9673537d73ce98a0 Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Wed, 29 Jan 2025 15:05:01 +0300 Subject: [PATCH 4/5] Clean up map page code --- mobile-v3/lib/src/app/learn/pages/lesson_finished.dart | 2 +- mobile-v3/lib/src/app/map/pages/map_page.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mobile-v3/lib/src/app/learn/pages/lesson_finished.dart b/mobile-v3/lib/src/app/learn/pages/lesson_finished.dart index e7098c6bfc..8ef0b579c4 100644 --- a/mobile-v3/lib/src/app/learn/pages/lesson_finished.dart +++ b/mobile-v3/lib/src/app/learn/pages/lesson_finished.dart @@ -11,7 +11,7 @@ class LessonFinishedWidget extends StatelessWidget { child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - Text("👋🏼 Great Job Jordan!", + Text("👋🏼 Great Job !", style: TextStyle(fontSize: 20, fontWeight: FontWeight.w700)), Text( "You can invite your friends to learn a thing about Air Pollution", diff --git a/mobile-v3/lib/src/app/map/pages/map_page.dart b/mobile-v3/lib/src/app/map/pages/map_page.dart index f5b41cb7b8..ac48a16a5d 100644 --- a/mobile-v3/lib/src/app/map/pages/map_page.dart +++ b/mobile-v3/lib/src/app/map/pages/map_page.dart @@ -47,7 +47,6 @@ class _MapScreenState extends State query = query.toLowerCase(); return measurements.where((measurement) { if (measurement.siteDetails != null) { - // Search through multiple location fields return (measurement.siteDetails!.city?.toLowerCase().contains(query) ?? false) || (measurement.siteDetails!.locationName @@ -183,6 +182,7 @@ class _MapScreenState extends State bool isModalFull = false; + void toggleModal(bool value) { void toggleModal(bool value) { if (isModalFull != value) { setState(() { From c7f6eeca40f2f0501f0776a76a27b7d3b9231bd7 Mon Sep 17 00:00:00 2001 From: Peter Kyeyune Date: Wed, 29 Jan 2025 15:14:56 +0300 Subject: [PATCH 5/5] Enhance search functionality in map page to include multiple location fields --- mobile-v3/lib/src/app/map/pages/map_page.dart | 56 +++---------------- 1 file changed, 7 insertions(+), 49 deletions(-) diff --git a/mobile-v3/lib/src/app/map/pages/map_page.dart b/mobile-v3/lib/src/app/map/pages/map_page.dart index ac48a16a5d..7809caaf72 100644 --- a/mobile-v3/lib/src/app/map/pages/map_page.dart +++ b/mobile-v3/lib/src/app/map/pages/map_page.dart @@ -47,6 +47,7 @@ class _MapScreenState extends State query = query.toLowerCase(); return measurements.where((measurement) { if (measurement.siteDetails != null) { + // Search through multiple location fields return (measurement.siteDetails!.city?.toLowerCase().contains(query) ?? false) || (measurement.siteDetails!.locationName @@ -55,9 +56,13 @@ class _MapScreenState extends State false) || (measurement.siteDetails!.name?.toLowerCase().contains(query) ?? false) || - (measurement.siteDetails!.searchName?.toLowerCase().contains(query) ?? + (measurement.siteDetails!.searchName + ?.toLowerCase() + .contains(query) ?? false) || - (measurement.siteDetails!.formattedName?.toLowerCase().contains(query) ?? + (measurement.siteDetails!.formattedName + ?.toLowerCase() + .contains(query) ?? false) || (measurement.siteDetails!.town?.toLowerCase().contains(query) ?? false) || @@ -182,7 +187,6 @@ class _MapScreenState extends State bool isModalFull = false; - void toggleModal(bool value) { void toggleModal(bool value) { if (isModalFull != value) { setState(() { @@ -453,53 +457,7 @@ class _MapScreenState extends State color: AppColors .boldHeadlineColor)), Row(children: [ - // Container( - // decoration: BoxDecoration( - // color: AppColors - // .highlightColor, - // borderRadius: - // BorderRadius - // .circular( - // 100)), - // height: 40, - // width: 52, - // child: Center( - // child: Padding( - // padding: - // const EdgeInsets - // .only( - // left: 8.0), - // child: Icon( - // size: 20, - // Icons - // .arrow_back_ios, - // color: AppColors - // .boldHeadlineColor, - // ), - // ), - // ), - // ), SizedBox(width: 8), - // Container( - // decoration: BoxDecoration( - // color: AppColors - // .highlightColor, - // borderRadius: - // BorderRadius - // .circular( - // 100)), - // height: 40, - // width: 52, - // child: Center( - // child: Icon( - // Icons - // .arrow_forward_ios, - // size: 20, - // color: AppColors - // .boldHeadlineColor, - // ), - // ), - // ) ]) ], ),