Files
ulapp/FieldLogger/ViewModels/AppStatusViewModel.cs
2026-08-23 20:54:03 -05:00

106 lines
3.4 KiB
C#

using System.ComponentModel;
using CommunityToolkit.Mvvm.ComponentModel;
using FieldLogger.Models;
using FieldLogger.Services;
namespace FieldLogger.ViewModels;
/// <summary>
/// Shared, glanceable hardware status shown above each primary workflow. The timer marks
/// position data stale even when the receiver stops sending without disconnecting.
/// </summary>
public sealed partial class AppStatusViewModel : ObservableObject
{
private static readonly TimeSpan StaleAfter = TimeSpan.FromSeconds(2);
private readonly DeviceConnectionManager _manager;
private readonly MaglinkService _gps;
[ObservableProperty]
private ConnectionState _locatorState;
[ObservableProperty]
private ConnectionState _gnssState;
[ObservableProperty]
private string _locatorStatus = "Locator disconnected";
[ObservableProperty]
private string _gnssStatus = "GNSS disconnected";
[ObservableProperty]
private string _positionSummary = "No GNSS fix · accuracy —";
public AppStatusViewModel(DeviceConnectionManager manager, MaglinkService gps)
{
_manager = manager;
_gps = gps;
_manager.PropertyChanged += OnConnectionPropertyChanged;
_gps.FixReceived += OnFixReceived;
Refresh();
}
private void OnConnectionPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName is nameof(DeviceConnectionManager.LocatorState)
or nameof(DeviceConnectionManager.GpsState)
or nameof(DeviceConnectionManager.LocatorName)
or nameof(DeviceConnectionManager.GpsName))
{
Refresh();
}
}
private void OnFixReceived(object? sender, GnssFix fix) => Refresh();
/// <summary>Refreshes status safely from device callbacks or a UI dispatcher timer.</summary>
public void Refresh()
{
var dispatcher = Application.Current?.Dispatcher;
if (dispatcher?.IsDispatchRequired == true)
{
dispatcher.Dispatch(RefreshCore);
return;
}
RefreshCore();
}
private void RefreshCore()
{
LocatorState = _manager.LocatorState;
GnssState = _manager.GpsState;
LocatorStatus = DeviceLabel("Locator", _manager.LocatorName, LocatorState);
GnssStatus = DeviceLabel("GNSS", _manager.GpsName, GnssState);
var fix = _gps.LatestFix;
if (fix is null)
{
PositionSummary = GnssState == ConnectionState.Connected
? "Waiting for fix · accuracy —"
: "No GNSS fix · accuracy —";
return;
}
var stale = DateTime.UtcNow - fix.ReceivedUtc > StaleAfter;
var staleLabel = stale ? " · STALE" : string.Empty;
PositionSummary = $"{fix.StatusLabel}{staleLabel} · H ±{fix.Hrms:F3} m · " +
$"V ±{fix.Vrms:F3} m · {fix.SatellitesUsed} sats · corr {fix.CorrectionAgeSeconds:F1} s";
}
private static string DeviceLabel(string kind, string? name, ConnectionState state)
{
var stateText = state switch
{
ConnectionState.Connected => "connected",
ConnectionState.Connecting => "connecting",
_ => "disconnected",
};
return state == ConnectionState.Disconnected || string.IsNullOrWhiteSpace(name)
? $"{kind} {stateText}"
: $"{kind} {stateText} · {name}";
}
}