Files
geo_front/lib/services/background_geo_service.dart
dmit.b 506608c508 Replace paid flutter_background_geolocation with free flutter_foreground_task
Replace proprietary flutter_background_geolocation (requires paid license
for Android release builds) with free flutter_foreground_task package.
Background location tracking now uses foreground service with periodic
geolocation updates every 30 seconds.
2026-06-25 13:24:24 +03:00

211 lines
5.5 KiB
Dart

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<bool> get isStarted async => await FlutterForegroundTask.isRunningService;
String? get token => _token;
String? get geoId => _geoId;
Future<void> 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<void> init() async {
if (_isReady) return;
await initNotifications();
FlutterForegroundTask.initCommunicationPort();
FlutterForegroundTask.addTaskDataCallback(_onTaskData);
_isReady = true;
}
Future<void> 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<void> stop() async {
await FlutterForegroundTask.stopService();
}
void dispose() {
_isReady = false;
_token = null;
_geoId = null;
}
static void _onTaskData(Object data) {}
Future<void> _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<void> _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<void> 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<void> onDestroy(DateTime timestamp) async {}
Future<void> _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');
}
}
}