let AI comment everything because well... yeah...

This commit is contained in:
Nico
2025-06-06 21:00:32 +02:00
parent 9c84d0c375
commit cc110ac104
44 changed files with 1230 additions and 646 deletions

View File

@@ -1,44 +1,69 @@
// * Service for managing local notifications in the app
// * Handles notification permissions, creation, and management
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
// * Notification service class that handles all notification functionality
class NotificationService {
// Plugin instance for local notifications
final notifiactionPlugin = FlutterLocalNotificationsPlugin();
// Initialization status flag
bool _isInitialized = false;
// Getter for initialization status
bool get isInitialized => _isInitialized;
// * Initialize the notification service
// * - Requests notification permissions
// * - Configures Android-specific settings
// * - Initializes the notification plugin
Future<void> initNotification() async {
// Prevent multiple initializations
if (_isInitialized) return;
// Request permissions for Android notifications
await notifiactionPlugin.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>()!.requestNotificationsPermission();
// Android-specific initialization settings
const initSettingsAndroid = AndroidInitializationSettings(
'@mipmap/ic_launcher',
'@mipmap/ic_launcher', // App icon for notifications
);
// Overall initialization settings
const initSettings = InitializationSettings(android: initSettingsAndroid);
// Initialize plugin
await notifiactionPlugin.initialize(initSettings);
_isInitialized = true;
}
// * Create default notification settings
// * Configures a notification with:
// * - Low importance and priority
// * - Ongoing status
// * - Specific channel for tracking
NotificationDetails notificationDetails() {
return const NotificationDetails(
android: AndroidNotificationDetails(
"tracking0",
"tracking ongoing",
"tracking0", // Channel ID
"tracking ongoing", // Channel name
importance: Importance.low,
priority: Priority.low,
ongoing: true,
ongoing: true, // Notification persists
),
);
}
// * Show a new notification
// * @param id The notification ID (default: 0)
// * @param title The notification title
Future<void> showNotification({int id = 0, String? title}) async {
return notifiactionPlugin.show(id, title, "", notificationDetails());
}
// * Delete an existing notification
// * @param id The ID of the notification to delete (default: 0)
Future<void> deleteNotification({id = 0}) async {
await notifiactionPlugin.cancel(id);
}

View File

@@ -8,13 +8,14 @@ import 'package:geolocator/geolocator.dart';
import 'package:latlong2/latlong.dart';
import 'package:shared_preferences/shared_preferences.dart';
/// Service, with the Singleton design pattern, that runs the geolocator service that tracks the position of the device.
/// This is needed for excursions
/// Service that runs the geolocator service to track the device's position
/// Uses the Singleton design pattern to ensure only one tracking instance exists
/// This service is essential for excursion tracking functionality
///
/// Start the tracking service via [startTracking]
/// Manage the position stream via [pauseTracking], [stopTracking] and [resumeTracking]
/// Start tracking via [startTracking]
/// Control tracking via [pauseTracking], [stopTracking], and [resumeTracking]
class TrackingService {
// Singleton stuff
// Singleton implementation
static TrackingService? _instance;
factory TrackingService() {
@@ -24,7 +25,7 @@ class TrackingService {
TrackingService._internal();
/// Resets all values, making it possible to start tracking again.
/// Reset all tracking values for a fresh start
static void resetInstance() {
if (_instance != null) {
_instance!.dispose();
@@ -32,36 +33,38 @@ class TrackingService {
}
}
// Variables
// - Stores the tracked coordinates
// Core tracking variables
// Stores tracked GPS coordinates
List<LatLng> pathList = [];
// - Stores all gotten accuracies
// Stores GPS accuracy values
List<double> accuracyList = [];
// - Stores timer so that is responsible vor the periodically tracking
// Timer for periodic tracking
Timer? _positionTimer;
// Current tracking status
bool isTracking = false;
// - Some more Singleton stuff (i guess. Vibecoded it because of lack of time)
// Context for UI interactions
BuildContext? _lastContext;
// Stream controllers for position and stats updates
final _positionController = StreamController<Position>.broadcast();
final _statsController = StreamController<TrackingStats>.broadcast();
// - Stores the last measured accuracy so that it can be displayed in the excursions view
double? currentAccuracy;
// - Getter
// Stream getters
Stream<Position> get positionStream$ => _positionController.stream;
Stream<TrackingStats> get statsStream$ => _statsController.stream;
// Name says it all
// Last measured GPS accuracy for display
double? currentAccuracy;
/// Calculate median accuracy from a list of accuracy values
/// This is preferred over mean because initial accuracy values can be very high (~9000m)
double _calculateMedianAccuracy(List<double> accuracies) {
// if one or less values for accuracy are available return that accuracy or 0
if (accuracies.isEmpty) return 0;
if (accuracies.length == 1) return accuracies.first;
// Copy the list so that the original data doesnt get modified
// Copy list to preserve original data
var sorted = List<double>.from(accuracies)..sort();
// Calculates median (not arithmetic mean!!). That is because often the firsed tracked accuracy is about 9000m
// Calculate median
if (sorted.length % 2 == 0) {
int midIndex = sorted.length ~/ 2;
return (sorted[midIndex - 1] + sorted[midIndex]) / 2;
@@ -70,13 +73,20 @@ class TrackingService {
}
}
/// Starts tracking
/// Start position tracking
/// - Initializes high-accuracy GPS tracking
/// - Sets up periodic position updates
/// - Shows tracking notification
Future<void> startTracking(BuildContext context) async {
if (isTracking) return;
// Configure high-accuracy GPS settings
final LocationSettings locationSettings =
LocationSettings(accuracy: LocationAccuracy.high);
_lastContext = context;
// Initialize and show tracking notification
await NotificationService().initNotification();
if (context.mounted) {
NotificationService().showNotification(
@@ -84,23 +94,25 @@ class TrackingService {
);
}
// Get tracking interval from settings
// Load tracking interval from settings
final prefs = await SharedPreferences.getInstance();
final intervalSeconds = prefs.getInt('trackingInterval') ?? 60;
// Create a timer that triggers position updates
// Set up periodic position updates
_positionTimer =
Timer.periodic(Duration(seconds: intervalSeconds), (_) async {
try {
final Position position = await Geolocator.getCurrentPosition(
locationSettings: locationSettings);
// Store position and accuracy data
pathList.add(LatLng(position.latitude, position.longitude));
accuracyList.add(position.accuracy);
currentAccuracy = position.accuracy;
_positionController.add(position);
_updateStats();
} catch (e) {
// Handle errors with notification
NotificationService().deleteNotification();
NotificationService().showNotification(title: "ERROR: $e");
}
@@ -124,9 +136,15 @@ class TrackingService {
isTracking = true;
}
// Last calculated tracking statistics
TrackingStats? _lastStats;
TrackingStats? get currentStats => _lastStats;
/// Update tracking statistics
/// Calculates:
/// - Current GPS accuracy
/// - Average accuracy
/// - Total distance traveled
void _updateStats() {
if (pathList.isEmpty) {
_lastStats = TrackingStats(
@@ -137,6 +155,7 @@ class TrackingService {
return;
}
// Calculate total distance
double totalDistance = 0;
for (int i = 1; i < pathList.length; i++) {
totalDistance += _calculateDistance(
@@ -155,19 +174,24 @@ class TrackingService {
_statsController.add(_lastStats!);
}
/// Request a manual stats update
void requestStatsUpdate() {
_updateStats();
}
/// Calculate distance between two GPS coordinates using the Haversine formula
/// @return Distance in meters
double _calculateDistance(
double lat1, double lon1, double lat2, double lon2) {
const double earthRadius = 6371000; // Erdradius in Metern
const double earthRadius = 6371000; // Earth radius in meters
// Convert coordinates to radians
double lat1Rad = lat1 * math.pi / 180;
double lat2Rad = lat2 * math.pi / 180;
double deltaLat = (lat2 - lat1) * math.pi / 180;
double deltaLon = (lon2 - lon1) * math.pi / 180;
// Haversine formula calculation
double a = math.sin(deltaLat / 2) * math.sin(deltaLat / 2) +
math.cos(lat1Rad) *
math.cos(lat2Rad) *
@@ -178,11 +202,13 @@ class TrackingService {
return earthRadius * c;
}
/// Temporarily pause tracking
void pauseTracking() {
_positionTimer?.cancel();
isTracking = false;
}
/// Resume paused tracking
void resumeTracking() {
if (!isTracking && _lastContext != null) {
startTracking(_lastContext!);
@@ -190,6 +216,7 @@ class TrackingService {
isTracking = true;
}
/// Stop tracking completely and clear current state
void stopTracking() {
_positionTimer?.cancel();
NotificationService().deleteNotification();
@@ -199,6 +226,7 @@ class TrackingService {
_lastContext = null;
}
/// Clear all recorded position data
void clearPath() {
pathList.clear();
accuracyList.clear();
@@ -206,10 +234,13 @@ class TrackingService {
_updateStats();
}
/// Convert tracked path to string format
/// Format: "latitude,longitude;latitude,longitude;..."
String getPathAsString() {
return pathList.map((pos) => "${pos.latitude},${pos.longitude}").join(";");
}
/// Clean up resources
void dispose() {
stopTracking();
_positionController.close();
@@ -217,6 +248,11 @@ class TrackingService {
}
}
/// Data class for tracking statistics
/// Contains:
/// - Current GPS accuracy
/// - Average accuracy
/// - Total distance in meters
class TrackingStats {
final double currentAccuracy;
final double averageAccuracy;