import 'package:flutter/material.dart'; import 'package:geocoding/geocoding.dart'; import 'package:geolocator/geolocator.dart'; import 'package:osm_nominatim/osm_nominatim.dart'; import '../models/location.dart'; import '../utils/helper.dart'; import 'text_with_bold.dart'; class LocationSelector extends StatefulWidget { final Function(MyLocation) onLocationChanged; // Callback function const LocationSelector({super.key, required this.onLocationChanged}); @override LocationSelectorState createState() => LocationSelectorState(); } class LocationSelectorState extends State { final TextEditingController _locationController = TextEditingController(); String _street = ''; String _country = ''; String? _administrativeArea; // DE: Bundesland String _city = ''; String? _subLocality; // DE: Stadtteil String? _postalCode; double? _latitude; double? _longitude; String? errorText; @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ElevatedButton.icon( icon: const Icon(Icons.my_location), onPressed: _getCurrentLocation, label: const Text('Current Position'), ), const SizedBox(height: 12), TextField( controller: _locationController, decoration: InputDecoration( labelText: 'Search location', suffixIcon: IconButton( icon: const Icon(Icons.search), onPressed: _searchLocation, ), ), ), const SizedBox(height: 6), Text( errorText != null ? '$errorText' : '', style: const TextStyle(color: Colors.red), ), const SizedBox(height: 20), TextWithBold(leadingText: 'Country:', boldText: ' $_country'), TextWithBold(leadingText: 'City:', boldText: ' $_city'), Text('Postal Code: ${_postalCode ?? ''}'), Text('Street: $_street'), Text('State/Area: ${_administrativeArea ?? ''}'), Text('Latitude: ${_latitude ?? '--'}'), Text('Longitude: ${_longitude ?? '--'}'), ], ), ); } void _searchLocation() async { String locationQuery = _locationController.text.trim(); if (locationQuery.isEmpty) { _resetLocationData('Specify an address for the search'); return; } try { if (isMobile) { List locations = await locationFromAddress(locationQuery); if (locations.isNotEmpty) { // Take first match Location firstLocation = locations.first; // Extract country and city information List placeMarks = await placemarkFromCoordinates( firstLocation.latitude, firstLocation.longitude); if (placeMarks.isNotEmpty) { Placemark placeMark = placeMarks.first; setState(() { _latitude = firstLocation.latitude; _longitude = firstLocation.longitude; _street = placeMark.street ?? ''; _country = placeMark.country ?? ''; _city = placeMark.locality ?? ''; _subLocality = placeMark.subLocality; _postalCode = placeMark.postalCode; _administrativeArea = placeMark.administrativeArea; errorText = null; }); // location is found, trigger callback triggerCallback(); } else { setState(() { // should placeMarks be empty return latitude and longitude anyway _latitude = firstLocation.latitude; _longitude = firstLocation.longitude; errorText = null; }); } } else { _resetLocationData('Location $locationQuery not found'); } } else { // other platforms than Android or iOS try { List searchResult = await Nominatim.searchByName( query: locationQuery, limit: 1, addressDetails: true, nameDetails: true, ); Map? addressData = searchResult.single.address; if (addressData != null) { String street = _getStreetInfo(addressData); String city = _getCityInfo(addressData); setState(() { _latitude = searchResult.single.lat; _longitude = searchResult.single.lon; _street = street; _country = addressData.containsKey('country') ? addressData['country'] : ''; _administrativeArea = addressData.containsKey('state') ? addressData['state'] : null; _city = city; _subLocality = addressData.containsKey('suburb') ? addressData['suburb'] : null; _postalCode = addressData.containsKey('postcode') ? addressData['postcode'] : null; errorText = null; }); triggerCallback(); } } catch (e) { _resetLocationData('Error looking up location $locationQuery: $e'); } } } catch (e) { _resetLocationData('Error searching location $locationQuery: $e'); } } void triggerCallback() { widget.onLocationChanged(MyLocation( street: _street, country: _country, administrativeArea: _administrativeArea, locality: _city, subLocality: _subLocality, postalCode: _postalCode, latitude: _latitude, longitude: _longitude, )); } /// Resets the location data and calls setState() void _resetLocationData(String errorMessage) { setState(() { errorText = errorMessage; _street = ''; _country = ''; _city = ''; _subLocality = null; _postalCode = null; _administrativeArea = null; _latitude = null; _longitude = null; }); } /// Determine users current position void _getCurrentLocation() async { bool serviceEnabled; LocationPermission permission; try { // Test if location services are enabled. serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { // Location services are not enabled, don't continue // accessing the position and request users of the // App to enable the location services. setState(() { errorText = 'Location services are disabled. ' 'Enable location services and try again.'; }); return; } permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { // Permissions are denied, next time you could try // requesting permissions again (this is also where // Android's shouldShowRequestPermissionRationale // returned true. According to Android guidelines // your App should show an explanatory UI now. setState(() { errorText = 'Location permissions are denied'; }); return; } } if (permission == LocationPermission.deniedForever) { // Permissions are denied forever, handle appropriately. setState(() { errorText = 'Location permissions are permanently denied, cannot request permissions.'; }); return; } } catch (e) { errorText = 'Error getting location permission: $e'; } try { // When we reach here, permissions are granted and we can // continue accessing the position of the device. Position position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high); if (isMobile) { // using package geocoding which works for Android and iOS only List placeMarks = await placemarkFromCoordinates( position.latitude, position.longitude); if (placeMarks.isNotEmpty) { Placemark placeMark = placeMarks.first; setState(() { _street = placeMark.street ?? ''; _country = placeMark.country ?? ''; _city = placeMark.locality ?? ''; _administrativeArea = placeMark.administrativeArea; _latitude = position.latitude; _longitude = position.longitude; errorText = null; }); triggerCallback(); } } else { // using osm_nominatim for other platforms than Android and iOS try { Place reverseSearchResult = await Nominatim.reverseSearch( lat: position.latitude, lon: position.longitude, addressDetails: true, nameDetails: true, ); Map? addressData = reverseSearchResult.address; if (addressData != null) { String street = _getStreetInfo(addressData); String city = _getCityInfo(addressData); setState(() { _latitude = position.latitude; _longitude = position.longitude; _street = street; _country = addressData.containsKey('country') ? addressData['country'] : ''; _administrativeArea = addressData.containsKey('state') ? addressData['state'] : null; _city = city; _subLocality = addressData.containsKey('suburb') ? addressData['suburb'] : null; _postalCode = addressData.containsKey('postcode') ? addressData['postcode'] : null; errorText = null; }); triggerCallback(); } } catch (e) { errorText = 'Error looking up current location: $e'; } } } catch (e) { errorText = 'Error getting current location: $e'; } } // Categories according to OSM tagging are not always consistent. // https://nominatim.org/release-docs/latest/api/Output/#addressdetails /// Try to get nominatim street information. String _getStreetInfo(Map addressData) { String street = ''; if (addressData.containsKey('road')) { street = addressData['road']; } else if (addressData.containsKey('locality')) { street = addressData['locality']; } else if (addressData.containsKey('city_block')) { street = addressData['city_block']; } return street; } /// Try to get nominatim city information. String _getCityInfo(Map addressData) { String city = ''; if (addressData.containsKey('city')) { city = addressData['city']; } else if (addressData.containsKey('village')) { city = addressData['village']; } else if (addressData.containsKey('town')) { city = addressData['town']; } return city; } }