- Implemented OpenStreetMap using WebView with Leaflet.js - Added OpenStreetMapView component with interactive map functionality - Created heat map visualization with color-coded intensity - Added 30 dummy location points around San Francisco Bay Area - Implemented location tracking with real-time pin placement - Added comprehensive UI with two-row button layout - Features: Start/Stop tracking, Center map, Demo heat map, Clear demo, Reset map - Added location count display and confirmation dialogs - Updated project structure and documentation - All functionality tested and working on Android emulator
317 lines
9.9 KiB
C#
317 lines
9.9 KiB
C#
using Microsoft.Extensions.Logging;
|
|
using LocationTrackerApp.Models;
|
|
using LocationTrackerApp.Data;
|
|
|
|
namespace LocationTrackerApp.Services;
|
|
|
|
/// <summary>
|
|
/// Service for tracking user location and storing data in SQLite database
|
|
/// </summary>
|
|
public class LocationService : ILocationService, IDisposable
|
|
{
|
|
private readonly LocationDbContext _dbContext;
|
|
private readonly ILogger<LocationService> _logger;
|
|
private CancellationTokenSource? _cancellationTokenSource;
|
|
private bool _isTracking;
|
|
private string? _currentSessionId;
|
|
private LocationData? _lastKnownLocation;
|
|
|
|
/// <summary>
|
|
/// Event fired when a new location is received
|
|
/// </summary>
|
|
public event EventHandler<LocationData>? LocationChanged;
|
|
|
|
/// <summary>
|
|
/// Event fired when location tracking status changes
|
|
/// </summary>
|
|
public event EventHandler<bool>? TrackingStatusChanged;
|
|
|
|
/// <summary>
|
|
/// Gets the current location tracking status
|
|
/// </summary>
|
|
public bool IsTracking => _isTracking;
|
|
|
|
/// <summary>
|
|
/// Gets the current session ID
|
|
/// </summary>
|
|
public string? CurrentSessionId => _currentSessionId;
|
|
|
|
/// <summary>
|
|
/// Initializes a new instance of LocationService
|
|
/// </summary>
|
|
/// <param name="dbContext">Database context for storing location data</param>
|
|
/// <param name="logger">Logger for recording service events</param>
|
|
public LocationService(LocationDbContext dbContext, ILogger<LocationService> logger)
|
|
{
|
|
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
|
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Starts location tracking
|
|
/// </summary>
|
|
/// <param name="sessionId">Optional session ID for grouping location data</param>
|
|
/// <returns>Task representing the async operation</returns>
|
|
public async Task StartTrackingAsync(string? sessionId = null)
|
|
{
|
|
try
|
|
{
|
|
if (_isTracking)
|
|
{
|
|
_logger.LogWarning("Location tracking is already active");
|
|
return;
|
|
}
|
|
|
|
// Check permissions first
|
|
if (!await HasLocationPermissionsAsync())
|
|
{
|
|
var granted = await RequestLocationPermissionsAsync();
|
|
if (!granted)
|
|
{
|
|
_logger.LogError("Location permissions not granted");
|
|
throw new UnauthorizedAccessException("Location permissions are required for tracking");
|
|
}
|
|
}
|
|
|
|
_currentSessionId = sessionId ?? Guid.NewGuid().ToString();
|
|
_cancellationTokenSource = new CancellationTokenSource();
|
|
|
|
_logger.LogInformation("Starting location tracking with session ID: {SessionId}", _currentSessionId);
|
|
|
|
// Start location tracking
|
|
var request = new GeolocationRequest
|
|
{
|
|
DesiredAccuracy = GeolocationAccuracy.Best,
|
|
Timeout = TimeSpan.FromSeconds(10)
|
|
};
|
|
|
|
// Start continuous location updates
|
|
_ = Task.Run(async () => await TrackLocationContinuouslyAsync(_cancellationTokenSource.Token));
|
|
|
|
_isTracking = true;
|
|
OnTrackingStatusChanged(true);
|
|
|
|
_logger.LogInformation("Location tracking started successfully");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to start location tracking");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stops location tracking
|
|
/// </summary>
|
|
/// <returns>Task representing the async operation</returns>
|
|
public async Task StopTrackingAsync()
|
|
{
|
|
try
|
|
{
|
|
if (!_isTracking)
|
|
{
|
|
_logger.LogWarning("Location tracking is not active");
|
|
return;
|
|
}
|
|
|
|
_logger.LogInformation("Stopping location tracking");
|
|
|
|
_cancellationTokenSource?.Cancel();
|
|
_cancellationTokenSource?.Dispose();
|
|
_cancellationTokenSource = null;
|
|
|
|
_isTracking = false;
|
|
_currentSessionId = null;
|
|
OnTrackingStatusChanged(false);
|
|
|
|
_logger.LogInformation("Location tracking stopped successfully");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to stop location tracking");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Gets the current location
|
|
/// </summary>
|
|
/// <returns>Current location data or null if not available</returns>
|
|
public async Task<LocationData?> GetCurrentLocationAsync()
|
|
{
|
|
try
|
|
{
|
|
if (!await HasLocationPermissionsAsync())
|
|
{
|
|
_logger.LogWarning("Location permissions not available");
|
|
return null;
|
|
}
|
|
|
|
var request = new GeolocationRequest
|
|
{
|
|
DesiredAccuracy = GeolocationAccuracy.Best,
|
|
Timeout = TimeSpan.FromSeconds(10)
|
|
};
|
|
|
|
var location = await Geolocation.GetLocationAsync(request);
|
|
if (location == null)
|
|
{
|
|
_logger.LogWarning("Unable to get current location");
|
|
return null;
|
|
}
|
|
|
|
var locationData = new LocationData
|
|
{
|
|
Latitude = location.Latitude,
|
|
Longitude = location.Longitude,
|
|
Accuracy = location.Accuracy ?? 0,
|
|
Altitude = location.Altitude,
|
|
Speed = location.Speed,
|
|
Timestamp = DateTime.UtcNow,
|
|
SessionId = _currentSessionId
|
|
};
|
|
|
|
_lastKnownLocation = locationData;
|
|
return locationData;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to get current location");
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Requests location permissions from the user
|
|
/// </summary>
|
|
/// <returns>True if permissions are granted, false otherwise</returns>
|
|
public async Task<bool> RequestLocationPermissionsAsync()
|
|
{
|
|
try
|
|
{
|
|
var status = await Permissions.RequestAsync<Permissions.LocationWhenInUse>();
|
|
var granted = status == PermissionStatus.Granted;
|
|
|
|
_logger.LogInformation("Location permission request result: {Status}", status);
|
|
return granted;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to request location permissions");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if location permissions are granted
|
|
/// </summary>
|
|
/// <returns>True if permissions are granted, false otherwise</returns>
|
|
public async Task<bool> HasLocationPermissionsAsync()
|
|
{
|
|
try
|
|
{
|
|
var status = await Permissions.CheckStatusAsync<Permissions.LocationWhenInUse>();
|
|
return status == PermissionStatus.Granted;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to check location permissions");
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Continuously tracks location updates
|
|
/// </summary>
|
|
/// <param name="cancellationToken">Cancellation token for stopping the tracking</param>
|
|
private async Task TrackLocationContinuouslyAsync(CancellationToken cancellationToken)
|
|
{
|
|
try
|
|
{
|
|
while (!cancellationToken.IsCancellationRequested && _isTracking)
|
|
{
|
|
var locationData = await GetCurrentLocationAsync();
|
|
if (locationData != null)
|
|
{
|
|
// Save to database
|
|
await SaveLocationDataAsync(locationData);
|
|
|
|
// Notify subscribers
|
|
OnLocationChanged(locationData);
|
|
}
|
|
|
|
// Wait before next update (adjust interval as needed)
|
|
await Task.Delay(TimeSpan.FromSeconds(5), cancellationToken);
|
|
}
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
_logger.LogInformation("Location tracking cancelled");
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error in continuous location tracking");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Saves location data to the database
|
|
/// </summary>
|
|
/// <param name="locationData">Location data to save</param>
|
|
private async Task SaveLocationDataAsync(LocationData locationData)
|
|
{
|
|
try
|
|
{
|
|
_dbContext.LocationData.Add(locationData);
|
|
await _dbContext.SaveChangesAsync();
|
|
|
|
_logger.LogDebug("Location data saved: Lat={Latitude}, Lng={Longitude}, Time={Timestamp}",
|
|
locationData.Latitude, locationData.Longitude, locationData.Timestamp);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Failed to save location data to database");
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the LocationChanged event
|
|
/// </summary>
|
|
/// <param name="locationData">The location data that changed</param>
|
|
protected virtual void OnLocationChanged(LocationData locationData)
|
|
{
|
|
LocationChanged?.Invoke(this, locationData);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Raises the TrackingStatusChanged event
|
|
/// </summary>
|
|
/// <param name="isTracking">Current tracking status</param>
|
|
protected virtual void OnTrackingStatusChanged(bool isTracking)
|
|
{
|
|
TrackingStatusChanged?.Invoke(this, isTracking);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes the service and stops tracking
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
try
|
|
{
|
|
if (_isTracking)
|
|
{
|
|
StopTrackingAsync().Wait();
|
|
}
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
_logger.LogError(ex, "Error disposing LocationService");
|
|
}
|
|
finally
|
|
{
|
|
_cancellationTokenSource?.Dispose();
|
|
}
|
|
}
|
|
}
|