import 'dart:async'; import 'package:flutter/material.dart'; import 'package:geocoding/geocoding.dart'; import 'package:objectid/objectid.dart'; import 'models/yourLocation.dart'; /// Open a places dialog for selecting a location using geocoding. /// Allows users to search for places by name and get coordinates. Future openPlacesDialog(GlobalKey sc) async { final BuildContext? context = sc.currentContext; if (context == null) { return YourLocation.noLocation; } final YourLocation? selectedLocation = await showDialog( context: context, builder: (BuildContext context) => const _PlaceSelectionDialog(), ); return selectedLocation ?? YourLocation.noLocation; } class _PlaceSelectionDialog extends StatefulWidget { const _PlaceSelectionDialog(); @override State<_PlaceSelectionDialog> createState() => _PlaceSelectionDialogState(); } class _PlaceSelectionDialogState extends State<_PlaceSelectionDialog> { final TextEditingController _searchController = TextEditingController(); List _searchResults = []; bool _isSearching = false; String? _errorMessage; @override void dispose() { _searchController.dispose(); super.dispose(); } Future _searchPlaces(String query) async { if (query.isEmpty) { setState(() { _searchResults = []; _errorMessage = null; }); return; } setState(() { _isSearching = true; _errorMessage = null; }); try { final List locations = await locationFromAddress(query); setState(() { _searchResults = locations; if (_searchResults.isEmpty) { _errorMessage = 'No places found. Try a different search.'; } }); } catch (e) { setState(() { _errorMessage = 'Search error: ${e.toString()}'; _searchResults = []; }); } finally { setState(() { _isSearching = false; }); } } Future _getPlaceName(Location location) async { try { final List placemarks = await placemarkFromCoordinates(location.latitude, location.longitude); if (placemarks.isNotEmpty) { final Placemark place = placemarks.first; final List parts = []; if (place.name != null && place.name!.isNotEmpty) parts.add(place.name!); if (place.locality != null && place.locality!.isNotEmpty) parts.add(place.locality!); if (place.country != null && place.country!.isNotEmpty) parts.add(place.country!); if (parts.isNotEmpty) { return parts.join(', '); } } } catch (e) { // Fall back to coordinates } return '${location.latitude.toStringAsFixed(4)}, ${location.longitude.toStringAsFixed(4)}'; } void _selectLocation(Location location) async { final String description = await _getPlaceName(location); if (!mounted) { return; } final YourLocation yourLocation = YourLocation( id: ObjectId(), lat: location.latitude, lon: location.longitude, description: description, distance: 10, ); Navigator.of(context).pop(yourLocation); } @override Widget build(BuildContext context) { return AlertDialog( title: const Text('Search for a place'), content: SizedBox( width: double.maxFinite, child: SingleChildScrollView( child: Column( mainAxisSize: MainAxisSize.min, children: [ TextField( controller: _searchController, decoration: InputDecoration( hintText: 'Enter city, address, or landmark', prefixIcon: const Icon(Icons.search), suffixIcon: _isSearching ? const SizedBox( width: 20, height: 20, child: Padding( padding: EdgeInsets.all(8.0), child: CircularProgressIndicator(strokeWidth: 2), ), ) : null, border: OutlineInputBorder( borderRadius: BorderRadius.circular(8), ), ), onChanged: (String value) { if (value.length > 2) { _searchPlaces(value); } else { setState(() { _searchResults = []; _errorMessage = null; }); } }, ), const SizedBox(height: 16), if (_errorMessage != null) Padding( padding: const EdgeInsets.all(8.0), child: Text( _errorMessage!, style: const TextStyle(color: Colors.red, fontSize: 12), ), ), if (_searchResults.isNotEmpty) SizedBox( height: 300, child: ListView.builder( shrinkWrap: true, itemCount: _searchResults.length, itemBuilder: (BuildContext context, int index) { final Location location = _searchResults[index]; return FutureBuilder( future: _getPlaceName(location), builder: (BuildContext context, AsyncSnapshot snapshot) { final String displayName = snapshot.data ?? '${location.latitude.toStringAsFixed(4)}, ${location.longitude.toStringAsFixed(4)}'; return ListTile( leading: const Icon(Icons.location_on), title: Text(displayName), subtitle: Text( '${location.latitude.toStringAsFixed(4)}, ${location.longitude.toStringAsFixed(4)}', style: const TextStyle(fontSize: 12), ), onTap: () => _selectLocation(location), ); }, ); }, ), ), ], ), ), ), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: const Text('Cancel'), ), ], ); } }