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"); } } }