import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'package:http/http.dart' as http; import '../errors/network_exception.dart'; import '../errors/parse_exception.dart'; import '../errors/server_exception.dart'; import 'nominatim_result.dart'; /// Tiny wrapper around the public Nominatim geocoder. Only used in the /// commute-settings flow to look up a home address; not called from any /// hot path. The User-Agent header is **required** by the Nominatim usage /// policy — without it the service throttles/blocks the client. class NominatimSearch { static const _userAgent = 'MarianumMobile/1.0 (contact@elias-mueller.com)'; static final Uri _base = Uri.parse('https://nominatim.openstreetmap.org/search'); /// Returns up to [limit] geocoded matches for the user-typed [query]. Future> run(String query, {int limit = 5}) async { final uri = _base.replace( queryParameters: { 'q': query, 'format': 'json', 'limit': limit.toString(), 'addressdetails': '0', 'accept-language': 'de', }, ); final http.Response response; try { response = await http .get(uri, headers: {'User-Agent': _userAgent, 'Accept': 'application/json'}) .timeout(const Duration(seconds: 15)); } on SocketException catch (e) { throw NetworkException(technicalDetails: 'nominatim: ${e.message}'); } on TimeoutException catch (e) { throw NetworkException.timeout(technicalDetails: 'nominatim: $e'); } on http.ClientException catch (e) { throw NetworkException(technicalDetails: 'nominatim: ${e.message}'); } if (response.statusCode > 299) { throw ServerException( statusCode: response.statusCode, technicalDetails: 'nominatim HTTP ${response.statusCode}', ); } try { final raw = jsonDecode(utf8.decode(response.bodyBytes)) as List; return raw .map((e) => _resultFromRaw(e as Map)) .toList(growable: false); } catch (e) { throw ParseException(technicalDetails: 'nominatim assemble: $e'); } } static NominatimResult _resultFromRaw(Map json) { // Nominatim returns lat/lon as strings, not numbers. Normalise here. final lat = double.parse(json['lat'].toString()); final lon = double.parse(json['lon'].toString()); return NominatimResult( displayName: json['display_name'] as String? ?? '?', lat: lat, lon: lon, ); } }