using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
using Microsoft.EntityFrameworkCore;
using LocationTrackerApp.Models;
using LocationTrackerApp.Services;
using LocationTrackerApp.Data;
using Microsoft.Extensions.Logging;
namespace LocationTrackerApp.ViewModels;
///
/// View model for the main view of the location tracking application
///
public class MainViewModel : INotifyPropertyChanged
{
private readonly ILocationService _locationService;
private readonly LocationDbContext _dbContext;
private readonly ILogger _logger;
private bool _isTracking;
private bool _isHeatMapVisible = true;
private bool _isPointsVisible = true;
private string _trackingStatusText = "Not Tracking";
private string _currentLocationText = "No location";
private string _locationCountText = "0 points";
private string _trackingButtonText = "Start Tracking";
private Color _trackingButtonColor = Colors.Green;
private LocationData? _currentLocation;
private int _locationCount;
///
/// Event fired when a property value changes
///
public event PropertyChangedEventHandler? PropertyChanged;
///
/// Gets or sets whether location tracking is active
///
public bool IsTracking
{
get => _isTracking;
set
{
if (_isTracking != value)
{
_isTracking = value;
OnPropertyChanged();
UpdateTrackingUI();
}
}
}
///
/// Gets or sets whether the heat map visualization is visible
///
public bool IsHeatMapVisible
{
get => _isHeatMapVisible;
set
{
if (_isHeatMapVisible != value)
{
_isHeatMapVisible = value;
OnPropertyChanged();
}
}
}
///
/// Gets or sets whether individual location points are visible
///
public bool IsPointsVisible
{
get => _isPointsVisible;
set
{
if (_isPointsVisible != value)
{
_isPointsVisible = value;
OnPropertyChanged();
}
}
}
///
/// Gets or sets the tracking status text
///
public string TrackingStatusText
{
get => _trackingStatusText;
set
{
if (_trackingStatusText != value)
{
_trackingStatusText = value;
OnPropertyChanged();
}
}
}
///
/// Gets or sets the current location text
///
public string CurrentLocationText
{
get => _currentLocationText;
set
{
if (_currentLocationText != value)
{
_currentLocationText = value;
OnPropertyChanged();
}
}
}
///
/// Gets or sets the location count text
///
public string LocationCountText
{
get => _locationCountText;
set
{
if (_locationCountText != value)
{
_locationCountText = value;
OnPropertyChanged();
}
}
}
///
/// Gets or sets the tracking button text
///
public string TrackingButtonText
{
get => _trackingButtonText;
set
{
if (_trackingButtonText != value)
{
_trackingButtonText = value;
OnPropertyChanged();
}
}
}
///
/// Gets or sets the tracking button color
///
public Color TrackingButtonColor
{
get => _trackingButtonColor;
set
{
if (_trackingButtonColor != value)
{
_trackingButtonColor = value;
OnPropertyChanged();
}
}
}
///
/// Command to toggle location tracking
///
public ICommand ToggleTrackingCommand { get; }
///
/// Command to toggle heat map visibility
///
public ICommand ToggleHeatMapCommand { get; }
///
/// Command to toggle points visibility
///
public ICommand TogglePointsCommand { get; }
///
/// Command to center map on user location
///
public ICommand CenterOnUserCommand { get; }
///
/// Command to clear all location data
///
public ICommand ClearDataCommand { get; }
///
/// Command to load location data
///
public ICommand LoadDataCommand { get; }
///
/// Command to open settings
///
public ICommand SettingsCommand { get; }
///
/// Initializes a new instance of MainViewModel
///
/// Location tracking service
/// Database context for location data
/// Logger for recording events
public MainViewModel(ILocationService locationService, LocationDbContext dbContext, ILogger logger)
{
_locationService = locationService ?? throw new ArgumentNullException(nameof(locationService));
_dbContext = dbContext ?? throw new ArgumentNullException(nameof(dbContext));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
// Initialize commands
ToggleTrackingCommand = new Command(async () => await ToggleTrackingAsync());
ToggleHeatMapCommand = new Command(() => IsHeatMapVisible = !IsHeatMapVisible);
TogglePointsCommand = new Command(() => IsPointsVisible = !IsPointsVisible);
CenterOnUserCommand = new Command(async () => await CenterOnUserAsync());
ClearDataCommand = new Command(async () => await ClearDataAsync());
LoadDataCommand = new Command(async () => await LoadDataAsync());
SettingsCommand = new Command(async () => await OpenSettingsAsync());
// Subscribe to location service events
_locationService.LocationChanged += OnLocationChanged;
_locationService.TrackingStatusChanged += OnTrackingStatusChanged;
// Initialize UI
UpdateTrackingUI();
_ = Task.Run(async () => await LoadLocationCountAsync());
}
///
/// Toggles location tracking on/off
///
private async Task ToggleTrackingAsync()
{
try
{
if (IsTracking)
{
await _locationService.StopTrackingAsync();
_logger.LogInformation("Location tracking stopped by user");
}
else
{
await _locationService.StartTrackingAsync();
_logger.LogInformation("Location tracking started by user");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to toggle location tracking");
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Failed to toggle location tracking", "OK");
}
}
///
/// Centers the map on the user's current location
///
private async Task CenterOnUserAsync()
{
try
{
var location = await _locationService.GetCurrentLocationAsync();
if (location != null)
{
// This would need to be implemented in the HeatMapView
_logger.LogInformation("Centering map on user location: {Latitude}, {Longitude}",
location.Latitude, location.Longitude);
}
else
{
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Unable to get current location", "OK");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to center on user location");
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Failed to center on user location", "OK");
}
}
///
/// Clears all location data from the database
///
private async Task ClearDataAsync()
{
try
{
var result = await Application.Current!.Windows[0].Page!.DisplayAlert(
"Clear Data",
"Are you sure you want to clear all location data? This action cannot be undone.",
"Yes",
"No");
if (result)
{
await _dbContext.ClearAllLocationDataAsync();
_locationCount = 0;
LocationCountText = "0 points";
_logger.LogInformation("All location data cleared by user");
await Application.Current.Windows[0].Page!.DisplayAlert("Success", "All location data has been cleared", "OK");
}
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to clear location data");
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Failed to clear location data", "OK");
}
}
///
/// Loads location data and updates the UI
///
private async Task LoadDataAsync()
{
try
{
await LoadLocationCountAsync();
_logger.LogInformation("Location data loaded by user");
await Application.Current!.Windows[0].Page!.DisplayAlert("Success", "Location data has been loaded", "OK");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load location data");
await Application.Current!.Windows[0].Page!.DisplayAlert("Error", "Failed to load location data", "OK");
}
}
///
/// Opens the settings page
///
private async Task OpenSettingsAsync()
{
try
{
// This would navigate to a settings page
_logger.LogInformation("Settings page requested by user");
await Application.Current!.Windows[0].Page!.DisplayAlert("Settings", "Settings page coming soon!", "OK");
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to open settings");
}
}
///
/// Handles location changed events from the location service
///
/// Event sender
/// New location data
private void OnLocationChanged(object? sender, LocationData locationData)
{
try
{
_currentLocation = locationData;
CurrentLocationText = $"Lat: {locationData.Latitude:F6}, Lng: {locationData.Longitude:F6}";
// Update location count
_locationCount++;
LocationCountText = $"{_locationCount} points";
_logger.LogDebug("Location updated: {Latitude}, {Longitude}", locationData.Latitude, locationData.Longitude);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to handle location changed event");
}
}
///
/// Handles tracking status changed events from the location service
///
/// Event sender
/// Current tracking status
private void OnTrackingStatusChanged(object? sender, bool isTracking)
{
try
{
IsTracking = isTracking;
_logger.LogInformation("Tracking status changed: {IsTracking}", isTracking);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to handle tracking status changed event");
}
}
///
/// Updates the tracking-related UI elements
///
private void UpdateTrackingUI()
{
if (IsTracking)
{
TrackingStatusText = "Tracking Active";
TrackingButtonText = "Stop Tracking";
TrackingButtonColor = Colors.Red;
}
else
{
TrackingStatusText = "Not Tracking";
TrackingButtonText = "Start Tracking";
TrackingButtonColor = Colors.Green;
}
}
///
/// Loads the current location count from the database
///
private async Task LoadLocationCountAsync()
{
try
{
_locationCount = await _dbContext.LocationData.CountAsync();
LocationCountText = $"{_locationCount} points";
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to load location count");
}
}
///
/// Raises the PropertyChanged event
///
/// Name of the property that changed
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
///
/// Disposes the view model and unsubscribes from events
///
public void Dispose()
{
try
{
_locationService.LocationChanged -= OnLocationChanged;
_locationService.TrackingStatusChanged -= OnTrackingStatusChanged;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error disposing MainViewModel");
}
}
}