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? _controller; StreamSubscription? _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 get positionStream { _controller = StreamController.broadcast(); _startListening(); return _controller!.stream; } Future _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 dispose() async { _retryTimer?.cancel(); await _positionSub?.cancel(); await _controller?.close(); _controller = null; } }