import 'dart:async'; import 'dart:convert'; import 'package:flutter/foundation.dart'; import 'package:flutter_background_geolocation/flutter_background_geolocation.dart' as bg; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:http/http.dart' as http; import '../config/api.dart'; class BackgroundGeoService { static final BackgroundGeoService _instance = BackgroundGeoService._init(); factory BackgroundGeoService() => _instance; BackgroundGeoService._init(); final http.Client _client = http.Client(); final FlutterLocalNotificationsPlugin _notifications = FlutterLocalNotificationsPlugin(); String? _token; String? _geoId; bool _isReady = false; bool _isStarted = false; bool _notificationsInitialized = false; bool get isReady => _isReady; bool get isStarted => _isStarted; String? get token => _token; String? get geoId => _geoId; Future initNotifications() async { if (_notificationsInitialized) return; const androidSettings = AndroidInitializationSettings( '@mipmap/ic_launcher', ); const iosSettings = DarwinInitializationSettings( requestAlertPermission: true, requestBadgePermission: true, requestSoundPermission: true, ); const initSettings = InitializationSettings( android: androidSettings, iOS: iosSettings, ); await _notifications.initialize( initSettings, onDidReceiveNotificationResponse: _onNotificationResponse, ); _notificationsInitialized = true; } void configure({required String token, required String geoId}) { _token = token; _geoId = geoId; } Future init() async { if (_isReady) return; await initNotifications(); bg.BackgroundGeolocation.onLocation(_onLocation, _onLocationError); bg.BackgroundGeolocation.onMotionChange(_onMotionChange); bg.BackgroundGeolocation.onProviderChange(_onProviderChange); bg.BackgroundGeolocation.onHttp(_onHttp); await bg.BackgroundGeolocation.ready(bg.Config( desiredAccuracy: bg.Config.DESIRED_ACCURACY_HIGH, distanceFilter: 10.0, stopOnTerminate: false, startOnBoot: true, debug: false, logLevel: bg.Config.LOG_LEVEL_OFF, autoSync: false, )); _isReady = true; } Future start() async { if (!_isReady) return; if (_isStarted) return; if (_token == null || _geoId == null || _geoId!.isEmpty) return; await bg.BackgroundGeolocation.start(); _isStarted = true; } Future stop() async { if (!_isStarted) return; await bg.BackgroundGeolocation.stop(); _isStarted = false; } void dispose() { bg.BackgroundGeolocation.removeListeners(); _isReady = false; _isStarted = false; _token = null; _geoId = null; } void _onLocation(bg.Location location) { if (_token == null || _geoId == null || _geoId!.isEmpty) return; if (location.sample == true) return; _sendPosition(location.coords.latitude, location.coords.longitude); } void _onLocationError(bg.LocationError error) { _showErrorNotification('Ошибка геолокации: $error'); } void _onMotionChange(bg.Location location) { kDebugMode? print('[BackgroundGeo] Motion changed: isMoving=${location.isMoving}'):{}; } void _onProviderChange(bg.ProviderChangeEvent event) { kDebugMode?print('[BackgroundGeo] Provider changed: $event'):{}; } void _onHttp(bg.HttpEvent event) { kDebugMode? print('[BackgroundGeo] HTTP: status=${event.status}, success=${event.success}'):{}; } Future _sendPosition(double latitude, double longitude) async { if (_token == null || _geoId == null || _geoId!.isEmpty) return; try { final response = await _client.put( Uri.parse('${ApiConfig.geoUrl}?id=$_geoId'), headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer $_token', }, body: jsonEncode({ 'x': longitude, 'y': latitude, }), ); if (response.statusCode != 200) { _showErrorNotification( 'Не удалось отправить позицию. Код ответа: ${response.statusCode}', ); } } catch (e) { _showErrorNotification('Ошибка отправки позиции: $e'); } } Future _showErrorNotification(String message) async { if (!_notificationsInitialized) return; const androidDetails = AndroidNotificationDetails( 'background_geo_errors', 'Background Geo Errors', channelDescription: 'Уведомления об ошибках геолокации в фоне', importance: Importance.high, priority: Priority.high, ticker: 'geo_error', ); const iosDetails = DarwinNotificationDetails( presentAlert: true, sound: 'default', ); const details = NotificationDetails( android: androidDetails, iOS: iosDetails, ); await _notifications.show( 0, 'Ошибка геолокации', message, details, payload: 'geo_error', ); } void _onNotificationResponse(NotificationResponse response) { print('[Notification] ${response.payload}'); } }