From a32ead31c218fad96273182c37d19c2b0ad27b43 Mon Sep 17 00:00:00 2001 From: Sicherhaven Date: Sat, 4 Apr 2026 18:43:02 +0530 Subject: [PATCH] =?UTF-8?q?fix:=20LOC=20=E2=80=94=20location=20filter=20ne?= =?UTF-8?q?ver=20applied=20to=20event=20API=20calls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause: SearchScreen popped with a plain city label string; the pincode was available in search results but discarded. home_screen only saved the display label to prefs and never updated the 'pincode' key, so every API call always sent {pincode:'all'} regardless of selection. GPS path had the same issue — lat/lng were obtained but thrown away after reverse-geocoding; only the label was passed back. Fix: - SearchScreen now pops with Map {label, pincode, lat?, lng?} instead of a plain String - Pincode results return their pincode; GPS returns actual coordinates; popular city chips look up the first matching pincode from the Kerala pincodes DB (fallback 'all' if not found) - home_screen._openLocationSearch() saves pincode + lat/lng to prefs and updates _pincode/_userLat/_userLng in state - home_screen._loadUserDataAndEvents() prefers getEventsByLocation (haversine) when GPS coords are saved, falls back to getEventsByPincode - EventsService gains getEventsByLocation(lat, lng) which sends latitude/longitude/radius_km to the existing Django haversine endpoint and auto-expands radius 10→25→50→100 km until ≥ 6 events found --- .../events/services/events_service.dart | 27 ++++++++++ lib/screens/home_screen.dart | 49 ++++++++++++++++--- lib/screens/search_screen.dart | 40 ++++++++++++--- 3 files changed, 101 insertions(+), 15 deletions(-) diff --git a/lib/features/events/services/events_service.dart b/lib/features/events/services/events_service.dart index f2b293b..fe1fb4b 100644 --- a/lib/features/events/services/events_service.dart +++ b/lib/features/events/services/events_service.dart @@ -108,6 +108,33 @@ class EventsService { return []; } + /// Get events by GPS coordinates using haversine distance filtering. + /// Automatically expands radius (10 → 25 → 50 → 100 km) until ≥ 6 events found. + Future> getEventsByLocation(double lat, double lng, {double initialRadiusKm = 10}) async { + const radii = [10.0, 25.0, 50.0, 100.0]; + for (final radius in radii) { + if (radius < initialRadiusKm) continue; + final body = { + 'latitude': lat, + 'longitude': lng, + 'radius_km': radius, + 'page': 1, + 'page_size': 50, + 'per_type': 5, + }; + final res = await _api.post(ApiEndpoints.eventsByPincode, body: body, requiresAuth: false); + final list = []; + final events = res['events'] ?? res['data'] ?? []; + if (events is List) { + for (final e in events) { + if (e is Map) list.add(EventModel.fromJson(Map.from(e))); + } + } + if (list.length >= 6 || radius >= 100) return list; + } + return []; + } + /// Events by month and year for calendar (POST to /events/events-by-month-year/) Future> getEventsByMonthYear(String month, int year) async { final res = await _api.post(ApiEndpoints.eventsByMonth, body: {'month': month, 'year': year}, requiresAuth: false); diff --git a/lib/screens/home_screen.dart b/lib/screens/home_screen.dart index d92936f..75d2d27 100644 --- a/lib/screens/home_screen.dart +++ b/lib/screens/home_screen.dart @@ -37,6 +37,8 @@ class _HomeScreenState extends State with SingleTickerProviderStateM String _username = ''; String _location = ''; String _pincode = 'all'; + double? _userLat; + double? _userLng; final EventsService _eventsService = EventsService(); @@ -104,12 +106,17 @@ class _HomeScreenState extends State with SingleTickerProviderStateM _location = storedLocation; } _pincode = prefs.getString('pincode') ?? 'all'; + _userLat = prefs.getDouble('user_lat'); + _userLng = prefs.getDouble('user_lng'); try { - // Fetch types and events in parallel for faster loading + // Fetch types and events in parallel for faster loading. + // Prefer haversine (lat/lng) when GPS coords are available; fall back to pincode. final results = await Future.wait([ _events_service_getEventTypesSafe(), - _events_service_getEventsSafe(_pincode), + (_userLat != null && _userLng != null) + ? _events_service_getEventsByLocationSafe(_userLat!, _userLng!) + : _events_service_getEventsSafe(_pincode), ]); final types = results[0] as List; final events = results[1] as List; @@ -171,6 +178,15 @@ class _HomeScreenState extends State with SingleTickerProviderStateM } } + Future> _events_service_getEventsByLocationSafe(double lat, double lng) async { + try { + return await _eventsService.getEventsByLocation(lat, lng); + } catch (_) { + // Fallback to all events if location-based fetch fails + return _events_service_getEventsSafe('all'); + } + } + Future _refresh() async { await _loadUserDataAndEvents(); } @@ -277,12 +293,31 @@ class _HomeScreenState extends State with SingleTickerProviderStateM transitionDuration: const Duration(milliseconds: 220), )); - if (selected != null && selected is String) { + if (selected != null && selected is Map) { + final label = (selected['label'] as String?) ?? 'Current Location'; + final pincode = (selected['pincode'] as String?) ?? 'all'; + final lat = selected['lat'] as double?; + final lng = selected['lng'] as double?; + final prefs = await SharedPreferences.getInstance(); - await prefs.setString('location', selected); - setState(() { - _location = selected; - }); + await prefs.setString('location', label); + await prefs.setString('pincode', pincode); + if (lat != null && lng != null) { + await prefs.setDouble('user_lat', lat); + await prefs.setDouble('user_lng', lng); + } else { + await prefs.remove('user_lat'); + await prefs.remove('user_lng'); + } + + if (mounted) { + setState(() { + _location = label; + _pincode = pincode; + _userLat = lat; + _userLng = lng; + }); + } await _refresh(); } } diff --git a/lib/screens/search_screen.dart b/lib/screens/search_screen.dart index 9d393c8..14e2c4b 100644 --- a/lib/screens/search_screen.dart +++ b/lib/screens/search_screen.dart @@ -106,8 +106,24 @@ class _SearchScreenState extends State { }); } + /// Pop with a structured result so home_screen can update both the display + /// label AND the pincode / GPS coordinates used for API filtering. + void _selectWithPincode(String label, {String? pincode}) { + Navigator.of(context).pop({ + 'label': label, + 'pincode': pincode ?? 'all', + }); + } + void _selectAndClose(String location) { - Navigator.of(context).pop(location); + // Legacy path for plain strings — wrap in structured map. + // Tries to look up a pincode from the database for the given city name. + final match = _locationDb.cast<_LocationItem?>().firstWhere( + (loc) => loc!.city.toLowerCase() == location.toLowerCase() || + loc.displayTitle.toLowerCase() == location.toLowerCase(), + orElse: () => null, + ); + _selectWithPincode(location, pincode: match?.pincode); } Future _useCurrentLocation() async { @@ -122,13 +138,14 @@ class _SearchScreenState extends State { if (permission == LocationPermission.deniedForever || permission == LocationPermission.denied) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('Location permission denied'))); - Navigator.of(context).pop('Current Location'); + Navigator.of(context).pop({'label': 'Current Location', 'pincode': 'all'}); } return; } final pos = await Geolocator.getCurrentPosition(desiredAccuracy: LocationAccuracy.best); + String label = 'Current Location'; try { final placemarks = await placemarkFromCoordinates(pos.latitude, pos.longitude); if (placemarks.isNotEmpty) { @@ -137,17 +154,24 @@ class _SearchScreenState extends State { if ((p.subLocality ?? '').isNotEmpty) parts.add(p.subLocality!); if ((p.locality ?? '').isNotEmpty) parts.add(p.locality!); if ((p.subAdministrativeArea ?? '').isNotEmpty) parts.add(p.subAdministrativeArea!); - final label = parts.isNotEmpty ? parts.join(', ') : 'Current Location'; - if (mounted) Navigator.of(context).pop(label); - return; + if (parts.isNotEmpty) label = parts.join(', '); } } catch (_) {} - if (mounted) Navigator.of(context).pop('Current Location'); + if (mounted) { + // Return lat/lng so home_screen can use haversine filtering + Navigator.of(context).pop({ + 'label': label, + 'pincode': 'all', + 'lat': pos.latitude, + 'lng': pos.longitude, + }); + } + return; } catch (e) { if (mounted) { ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text(userFriendlyError(e)))); - Navigator.of(context).pop('Current Location'); + Navigator.of(context).pop({'label': 'Current Location', 'pincode': 'all'}); } } finally { if (mounted) setState(() => _loadingLocation = false); @@ -320,7 +344,7 @@ class _SearchScreenState extends State { subtitle: loc.pincode != null && loc.pincode!.isNotEmpty ? Text(loc.pincode!, style: TextStyle(color: Colors.grey[500], fontSize: 13)) : null, - onTap: () => _selectAndClose(loc.returnValue), + onTap: () => _selectWithPincode(loc.displayTitle, pincode: loc.pincode), ); }, ),