133 lines
4.4 KiB
Dart
133 lines
4.4 KiB
Dart
import 'dart:async';
|
|
import 'package:geolocator/geolocator.dart';
|
|
import 'location_source.dart';
|
|
|
|
/// Telefon beépített GPS-ét használó helymeghatározási forrás.
|
|
///
|
|
/// Android háttér-működéshez a [flutter_foreground_task] kezeli
|
|
/// az előtér-szolgáltatást (notification), ez az osztály csak
|
|
/// a Geolocator streamet konfigurálja.
|
|
class PhoneGpsSource implements LocationSource {
|
|
@override
|
|
String get displayName => 'Telefon GPS';
|
|
|
|
StreamController<SourcePosition>? _controller;
|
|
StreamSubscription<Position>? _positionSub;
|
|
|
|
/// Frissítési intervallum ms-ban.
|
|
final int intervalMs;
|
|
|
|
/// Minimális elmozdulás méterben új pont előtt.
|
|
final double distanceFilter;
|
|
|
|
/// Egymást követő sikertelen újraindítási kísérletek száma — véd a
|
|
/// végtelen, szoros hiba-ciklus ellen, ha valami tartósan elromlott.
|
|
int _retryCount = 0;
|
|
static const _maxRetries = 5;
|
|
Timer? _retryTimer;
|
|
|
|
PhoneGpsSource({
|
|
this.intervalMs = 1000,
|
|
this.distanceFilter = 1.0,
|
|
});
|
|
|
|
@override
|
|
bool get isAvailable => _controller != null && !(_controller!.isClosed);
|
|
|
|
@override
|
|
Stream<SourcePosition> get positionStream {
|
|
_controller = StreamController<SourcePosition>.broadcast();
|
|
|
|
_startListening();
|
|
return _controller!.stream;
|
|
}
|
|
|
|
Future<void> _startListening() async {
|
|
// Engedélyek ellenőrzése
|
|
LocationPermission permission = await Geolocator.checkPermission();
|
|
if (permission == LocationPermission.denied) {
|
|
permission = await Geolocator.requestPermission();
|
|
}
|
|
if (permission == LocationPermission.deniedForever ||
|
|
permission == LocationPermission.denied) {
|
|
_controller?.addError(
|
|
Exception('Helymeghatározási engedély megtagadva. '
|
|
'Kérjük, engedélyezze a beállításokban.'),
|
|
);
|
|
return;
|
|
}
|
|
|
|
final settings = AndroidSettings(
|
|
accuracy: LocationAccuracy.high,
|
|
distanceFilter: distanceFilter.toInt(),
|
|
intervalDuration: Duration(milliseconds: intervalMs),
|
|
// Háttér-helymeghatározáshoz szükséges — a foreground_task
|
|
// notification biztosítja a jogszerű háttér-használatot.
|
|
foregroundNotificationConfig: const ForegroundNotificationConfig(
|
|
notificationText: 'Track rögzítése folyamatban',
|
|
notificationTitle: 'Terepi Segéd - Nyomvonal',
|
|
enableWakeLock: true,
|
|
),
|
|
);
|
|
|
|
await _positionSub?.cancel();
|
|
|
|
_positionSub = Geolocator.getPositionStream(
|
|
locationSettings: settings,
|
|
).listen((Position pos) {
|
|
_retryCount = 0;
|
|
_controller?.add(SourcePosition(
|
|
latitude: pos.latitude,
|
|
longitude: pos.longitude,
|
|
altitude: pos.altitude,
|
|
accuracy: pos.accuracy,
|
|
verticalAccuracy: pos.altitudeAccuracy,
|
|
speed: pos.speed,
|
|
heading: pos.heading,
|
|
timestamp: pos.timestamp,
|
|
source: displayName,
|
|
));
|
|
},
|
|
onError: (e) => _handleStreamError,
|
|
onDone: () =>
|
|
_handleStreamError(Exception('A GPS-stream váratlanul lezárult.')));
|
|
}
|
|
|
|
/// A Geolocator stream NEM öngyógyuló: ha egyszer hibát dob (terepen,
|
|
/// zötykölődő telefonnal ez elő szokott fordulni), magától többé NEM
|
|
/// küld pozíciót. Ezért itt — a korábbi "csak továbbadjuk és feladjuk"
|
|
/// helyett — manuálisan újraindítjuk a mögöttes streamet. Végleges,
|
|
/// nem-helyreállítható hibáknál (jogosultság/szolgáltatás kikapcsolva)
|
|
/// viszont TOVÁBBADJUK, hogy a TrackingController korrekten leállítsa
|
|
/// a rögzítést, ahogy eddig is.
|
|
void _handleStreamError(Object e) {
|
|
if (e is LocationServiceDisabledException ||
|
|
e is PermissionDeniedException) {
|
|
_controller?.addError(e);
|
|
return;
|
|
}
|
|
|
|
_retryCount++;
|
|
if (_retryCount > _maxRetries) {
|
|
// Tartósan hibás állapot — ezt már valóban jelezni kell.
|
|
_controller?.addError(e);
|
|
return;
|
|
}
|
|
|
|
// Rövid várakozás, majd friss stream indítása. A _controller
|
|
// (amit a TrackingController figyel) változatlan marad — onnan
|
|
// nézve ez csak egy pillanatnyi szünet a pozíciókban, nem egy
|
|
// végleges leállás.
|
|
_retryTimer?.cancel();
|
|
_retryTimer = Timer(const Duration(seconds: 1), _startListening);
|
|
}
|
|
|
|
@override
|
|
Future<void> dispose() async {
|
|
_retryTimer?.cancel();
|
|
await _positionSub?.cancel();
|
|
await _controller?.close();
|
|
_controller = null;
|
|
}
|
|
}
|