import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; /// Supplies the device's *approximate* location (or null when unavailable or /// denied). Behind an interface so the UI is fakeable in tests and the concrete /// platform code stays at the composition root. The caller immediately reduces /// the result to a low-precision geohash — a precise fix never leaves the device. abstract interface class CoarseLocationProvider { Future<({double lat, double lon})?> currentCoarseLatLon(); } /// Platform-channel implementation backed by Android's own LocationManager — /// deliberately no Google Play Services, so the APK stays free of proprietary /// classes (F-Droid inclusion). The native side handles the permission prompt, /// a coarse single fix with a timeout, and a last-known-position fallback. /// Returns null on any denial/error rather than throwing, so the UI can /// degrade quietly. Android only; other platforms return null. class NativeCoarseLocation implements CoarseLocationProvider { const NativeCoarseLocation(); static const _channel = MethodChannel('org.comunes.tane/coarse_location'); @override Future<({double lat, double lon})?> currentCoarseLatLon() async { if (defaultTargetPlatform != TargetPlatform.android) return null; try { // Safety net over the native 12s fix timeout; the permission prompt can // legitimately keep the call open for a while, hence the wide margin. final result = await _channel .invokeMapMethod('getCoarseLatLon') .timeout(const Duration(seconds: 60)); final lat = result?['lat']; final lon = result?['lon']; if (lat == null || lon == null) return null; return (lat: lat, lon: lon); } catch (_) { return null; // denied, timeout, channel error… } } }