cofounderella/lib/components/location_selector.dart

334 lines
11 KiB
Dart

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';
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<LocationSelector> {
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),
Text('Country: $_country'),
Text('City: $_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<Location> locations = await locationFromAddress(locationQuery);
if (locations.isNotEmpty) {
// Take first match
Location firstLocation = locations.first;
// Extract country and city information
List<Placemark> 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<Place> searchResult = await Nominatim.searchByName(
query: locationQuery,
limit: 1,
addressDetails: true,
nameDetails: true,
);
Map<String, dynamic>? 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<Placemark> 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<String, dynamic>? 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<String, dynamic> 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<String, dynamic> 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;
}
}