506608c508
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.
211 lines
5.5 KiB
Dart
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');
|
|
}
|
|
}
|
|
}
|