tane/apps/app_seeds/lib/services/coarse_location.dart
vjrj d0fabb80a9 fix(android): drop geolocator for a native LocationManager channel
Removes the geolocator dependency, which transitively embedded
com.google.android.gms.* (play-services-location) — proprietary classes
that F-Droid's scanner rejects. The optional coarse-location button now
talks to Android's own LocationManager over a small platform channel
(org.comunes.tane/coarse_location): permission prompt, single coarse fix
with a 12s timeout, last-known fallback, null on any failure. Behavior
is unchanged and the CoarseLocationProvider interface is preserved, so
the UI, i18n and widget tests need no changes.

Also sets dependenciesInfo.includeInApk/Bundle = false so the AGP
dependency-metadata block (also flagged by F-Droid) is not embedded.
Neither change affects the Google Play build.
2026-07-17 09:19:05 +02:00

40 lines
1.8 KiB
Dart

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<String, double>('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…
}
}
}