import 'dart:async'; import 'dart:convert'; import 'package:flutter_foreground_task/flutter_foreground_task.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:geolocator/geolocator.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 _notificationsInitialized = false; bool get isReady => _isReady; Future get isStarted async => await FlutterForegroundTask.isRunningService; String? get token => _token; String? get geoId => _geoId; Future initNotifications() async { if (_notificationsInitialized) return; const androidSettings = AndroidInitializationSettings( '@mipmap/ic_launcher', ); const iosSettings = DarwinInitializationSettings(); 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(); FlutterForegroundTask.initCommunicationPort(); FlutterForegroundTask.addTaskDataCallback(_onTaskData); _isReady = true; } Future start() async { if (!_isReady) return; if (_token == null || _geoId == null || _geoId!.isEmpty) return; final androidOptions = AndroidNotificationOptions( channelId: 'background_geo_channel', channelName: 'Background Geolocation', channelDescription: 'Отслеживание позиции в фоновом режиме', channelImportance: NotificationChannelImportance.HIGH, priority: NotificationPriority.HIGH, ); final iosOptions = IOSNotificationOptions( showNotification: true, playSound: true, ); final taskOptions = ForegroundTaskOptions( eventAction: ForegroundTaskEventAction.repeat(15000), autoRunOnBoot: true, allowWakeLock: true, ); FlutterForegroundTask.init( androidNotificationOptions: androidOptions, iosNotificationOptions: iosOptions, foregroundTaskOptions: taskOptions, ); await FlutterForegroundTask.startService( notificationTitle: 'Family Safety', notificationText: 'Отслеживание позиции активно', ); } Future stop() async { await FlutterForegroundTask.stopService(); } void dispose() { _isReady = false; _token = null; _geoId = null; } static void _onTaskData(Object data) {} 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(); const details = NotificationDetails( android: androidDetails, iOS: iosDetails, ); await _notifications.show( 0, 'Ошибка геолокации', message, details, payload: 'geo_error', ); } void _onNotificationResponse(NotificationResponse response) { print('[Notification] ${response.payload}'); } } class BackgroundTaskHandler extends TaskHandler { int _locationCount = 0; @override Future onStart(DateTime timestamp, TaskStarter starter) async {} @override void onRepeatEvent(DateTime timestamp) { _locationCount++; if (_locationCount % 2 != 0) return; _getLocationAndSend(); } @override void onReceiveData(Object data) {} @override void onNotificationButtonPressed(String id) {} @override void onNotificationPressed() {} @override void onNotificationDismissed() {} @override Future onDestroy(DateTime timestamp) async {} Future _getLocationAndSend() async { try { final position = await Geolocator.getCurrentPosition(); final bgGeo = BackgroundGeoService(); await bgGeo._sendPosition(position.latitude, position.longitude); } catch (e) { print('[BackgroundGeo] Error getting location: $e'); } } }