import 'dart:async'; import 'dart:convert'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:geolocator/geolocator.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:intl/intl.dart'; import 'WebSocketManager.dart'; import 'SharedpreferencesService.dart'; class BackgroundLocationService { static bool? _lastNetworkStatus; // Track last network status static DateTime? _lastNotificationTime; // Track last notification time static const Duration _notificationDebounceDuration = Duration(seconds: 30); static const String customChannelId = 'GEN ERP flutter'; static const String customChannelName = 'GEN ERP flutter'; static const String customChannelDescription = 'GEN ERP flutter'; static const int notificationId = 0; static Timer? _locationTimer; static final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin(); static StreamSubscription? _positionStream; WebSocketManager webSocketManager = WebSocketManager( onConnectSuccess: () {}, onMessage: (message) {}, onClose: () {}, onConnectFailed: () {}, ); String? empId; String? sessionId; static Future init() async { try { const InitializationSettings initializationSettings = InitializationSettings( android: AndroidInitializationSettings('@mipmap/ic_launcher'), iOS: DarwinInitializationSettings( requestAlertPermission: false, requestBadgePermission: false, requestSoundPermission: false, ), ); await flutterLocalNotificationsPlugin.initialize(initializationSettings); const AndroidNotificationChannel androidChannel = AndroidNotificationChannel( customChannelId, customChannelName, description: customChannelDescription, importance: Importance.max, playSound: false, ); await flutterLocalNotificationsPlugin .resolvePlatformSpecificImplementation< AndroidFlutterLocalNotificationsPlugin>() ?.createNotificationChannel(androidChannel); } catch (e) { print("Error initializing notifications: $e"); } } static Future checkAndRequestLocationPermissions(BuildContext context) async { try { bool serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Please enable GPS")), ); await Geolocator.openLocationSettings(); return; } LocationPermission permission = await Geolocator.checkPermission(); if (permission == LocationPermission.denied) { permission = await Geolocator.requestPermission(); if (permission == LocationPermission.denied) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Location permission denied")), ); return; } } if (permission == LocationPermission.deniedForever) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text("Location permission permanently denied")), ); await Geolocator.openAppSettings(); return; } } catch (e) { print("Error requesting location permissions: $e"); } } static Future showNotification(String title, String message) async { const AndroidNotificationDetails androidPlatformChannelSpecifics = AndroidNotificationDetails( customChannelId, customChannelName, importance: Importance.defaultImportance, ticker: 'ticker', ongoing: true, playSound: false, ); const DarwinNotificationDetails darwinNotificationDetails = DarwinNotificationDetails(presentSound: false); const NotificationDetails platformChannelSpecifics = NotificationDetails( android: androidPlatformChannelSpecifics, iOS: darwinNotificationDetails, ); final pendingNotifications = await flutterLocalNotificationsPlugin.pendingNotificationRequests(); final notificationAlreadyExists = pendingNotifications.any((notification) => notification.id == notificationId); await flutterLocalNotificationsPlugin.show( notificationId, title, message, platformChannelSpecifics, ); } static Future hideNotification() async { await flutterLocalNotificationsPlugin.cancel(notificationId); } void initWebSocket() { Future.delayed(Duration.zero, () { webSocketManager.connect(); }); } static Future checkGpsStatus() async { return await Geolocator.isLocationServiceEnabled(); } static Future checkNetworkStatus() async { var connectivityResult = await Connectivity().checkConnectivity(); return connectivityResult != ConnectivityResult.none; } static Future startLocationService(BuildContext context) async { await init(); bool isGpsEnabled = await checkGpsStatus(); bool isNetworkAvailable = await checkNetworkStatus(); if (isGpsEnabled && isNetworkAvailable) { await checkAndRequestLocationPermissions(context); showNotification("GEN ERP", "You're Online!"); _positionStream?.cancel(); // Cancel any existing stream _positionStream = Geolocator.getPositionStream( locationSettings: LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 50, ), ).listen((Position position) async { final location = Location( latitude: position.latitude, longitude: position.longitude, altitude: position.altitude, accuracy: position.accuracy, bearing: position.heading, speed: position.speed, time: position.timestamp.millisecondsSinceEpoch.toDouble(), isMock: position.isMocked, ); await handleLocationUpdate(location, context); }, onError: (e) { print("Location stream error: $e"); showNotification("GEN ERP", "Location error: $e"); }); } else { if (!isGpsEnabled) { showNotification("GEN ERP", "You're Offline! Check your GPS connection."); await checkAndRequestLocationPermissions(context); } if (!isNetworkAvailable) { showNotification("GEN ERP", "You're Offline! Check your network connection."); } } _locationTimer?.cancel(); _locationTimer = Timer.periodic(Duration(seconds: 30), (timer) async { if (await checkGpsStatus() && await checkNetworkStatus()) { final position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, ); final location = Location( latitude: position.latitude, longitude: position.longitude, altitude: position.altitude, accuracy: position.accuracy, bearing: position.heading, speed: position.speed, time: position.timestamp.millisecondsSinceEpoch.toDouble(), isMock: position.isMocked, ); await handleLocationUpdate(location, context); } }); } static Future stopLocationService() async { print("Background location service stopped"); await hideNotification(); _locationTimer?.cancel(); _positionStream?.cancel(); } static Future isServiceRunning() async { return _positionStream != null && _positionStream!.isPaused == false; } Future getCurrentLocation(BuildContext context) async { empId = await SharedpreferencesService().getString("UserId"); sessionId = await SharedpreferencesService().getString("Session_id"); saveLastLocationTime(); try { final position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, ); return Location( latitude: position.latitude, longitude: position.longitude, altitude: position.altitude, accuracy: position.accuracy, bearing: position.heading, speed: position.speed, time: position.timestamp.millisecondsSinceEpoch.toDouble(), isMock: position.isMocked, ); } catch (e) { print("Error getting current location: $e"); showNotification("GEN ERP", "Error getting location: $e"); rethrow; } } static Future handleLocationUpdate(Location location, BuildContext context) async { final empId = await SharedpreferencesService().getString("UserId"); final sessionId = await SharedpreferencesService().getString("Session_id"); final webSocketManager = WebSocketManager( onConnectSuccess: () {}, onMessage: (message) {}, onClose: () {}, onConnectFailed: () {}, ); bool isNetworkAvailable = await webSocketManager.isNetworkAvailable(); DateTime now = DateTime.now(); // Check if enough time has passed since the last notification (debouncing) if (_lastNotificationTime != null && now.difference(_lastNotificationTime!).inSeconds < _notificationDebounceDuration.inSeconds) { return; // Skip notification if within debounce duration } if (_lastNetworkStatus != isNetworkAvailable) { if (isNetworkAvailable) { webSocketManager.sendMessage(jsonEncode({ "command": "server_request", "route": "attendenece_live_location_update", "session_id": sessionId, "ref_data": { "session_id": sessionId, "location": "${location.latitude},${location.longitude}", "speed": location.speed, "altitude": location.altitude, "direction": location.bearing, "direction_accuracy": 0.0, // Geolocator doesn't provide this directly "altitude_accuracy": 0.0, // Geolocator doesn't provide this directly "speed_accuracy": 0.0, // Geolocator doesn't provide this directly "location_accuracy": location.accuracy, "location_provider": "", } })); showNotification("GEN ERP", "You're Online!"); }else { showNotification("GEN ERP", "You're Offline!"); } _lastNetworkStatus = isNetworkAvailable; _lastNotificationTime = now; } saveLastLocationTime(); } } class Location { double? latitude; double? longitude; double? altitude; double? bearing; double? accuracy; double? speed; double? time; bool? isMock; Location({ required this.latitude, required this.longitude, required this.altitude, required this.accuracy, required this.bearing, required this.speed, required this.time, required this.isMock, }); Map toMap() { return { 'latitude': latitude, 'longitude': longitude, 'altitude': altitude, 'bearing': bearing, 'accuracy': accuracy, 'speed': speed, 'time': time, 'is_mock': isMock, }; } } void saveLastLocationTime() { var currentTime = DateTime.now(); var formatter = DateFormat('HH:mm:ss').format(currentTime); SharedpreferencesService().saveString("lastLocationTime", formatter); }