Initial commit: FieldLogger MAUI app with Maglink BLE support
Added Maglink RTK GNSS receiver integration with correct BLE UUIDs and device name filtering (ML-* prefix). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
149
FieldLogger/ViewModels/HomeViewModel.cs
Normal file
149
FieldLogger/ViewModels/HomeViewModel.cs
Normal file
@@ -0,0 +1,149 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using FieldLogger.Models;
|
||||
using FieldLogger.Services;
|
||||
using FieldLogger.Services.Data;
|
||||
|
||||
namespace FieldLogger.ViewModels;
|
||||
|
||||
public sealed partial class HomeViewModel : ObservableObject
|
||||
{
|
||||
private readonly DeviceConnectionManager _manager;
|
||||
private readonly MaglinkService _gps;
|
||||
private readonly PointLogger _pointLogger;
|
||||
private readonly AppDatabase _database;
|
||||
private readonly SettingsService _settings;
|
||||
|
||||
public DeviceConnectionManager Manager => _manager;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _activeJobName = "No active job";
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _hasActiveJob;
|
||||
|
||||
[ObservableProperty]
|
||||
private int _pointCount;
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fixSummary = "Waiting for GPS…";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fixStatusLabel = "—";
|
||||
|
||||
[ObservableProperty]
|
||||
private string _fixAccuracy = "";
|
||||
|
||||
public ObservableCollection<LoggedPoint> RecentPoints { get; } = new();
|
||||
|
||||
public HomeViewModel(DeviceConnectionManager manager, MaglinkService gps,
|
||||
PointLogger pointLogger, AppDatabase database, SettingsService settings)
|
||||
{
|
||||
_manager = manager;
|
||||
_gps = gps;
|
||||
_pointLogger = pointLogger;
|
||||
_database = database;
|
||||
_settings = settings;
|
||||
|
||||
_gps.FixReceived += OnFixReceived;
|
||||
_pointLogger.PointSaved += OnPointSaved;
|
||||
_pointLogger.PacketIgnoredNoJob += OnPacketIgnoredNoJob;
|
||||
}
|
||||
|
||||
/// <summary>Called from the page's OnAppearing.</summary>
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
// First launch: nothing saved yet, send the user to device selection.
|
||||
if (!_manager.HasSavedDevice(DeviceKind.Locator) && !_manager.HasSavedDevice(DeviceKind.RtkGps))
|
||||
{
|
||||
await Shell.Current.GoToAsync($"devicescan?kind={DeviceKind.Locator}");
|
||||
return;
|
||||
}
|
||||
|
||||
_manager.Start();
|
||||
await RefreshJobAsync();
|
||||
}
|
||||
|
||||
public async Task RefreshJobAsync()
|
||||
{
|
||||
var jobId = _settings.ActiveJobId;
|
||||
if (jobId is null)
|
||||
{
|
||||
HasActiveJob = false;
|
||||
ActiveJobName = "No active job";
|
||||
PointCount = 0;
|
||||
RecentPoints.Clear();
|
||||
return;
|
||||
}
|
||||
|
||||
var job = await _database.GetJobAsync(jobId.Value);
|
||||
if (job is null)
|
||||
{
|
||||
_settings.ActiveJobId = null;
|
||||
await RefreshJobAsync();
|
||||
return;
|
||||
}
|
||||
|
||||
HasActiveJob = true;
|
||||
ActiveJobName = job.Name;
|
||||
PointCount = await _database.GetPointCountAsync(job.Id);
|
||||
|
||||
var points = await _database.GetPointsAsync(job.Id);
|
||||
RecentPoints.Clear();
|
||||
foreach (var p in points.OrderByDescending(p => p.TimestampUtc).Take(10))
|
||||
RecentPoints.Add(p);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task NewJobAsync()
|
||||
{
|
||||
var name = await Shell.Current.DisplayPromptAsync("New Job", "Job name:",
|
||||
placeholder: $"Job {DateTime.Now:yyyy-MM-dd}");
|
||||
if (string.IsNullOrWhiteSpace(name))
|
||||
return;
|
||||
|
||||
var job = await _database.CreateJobAsync(name.Trim());
|
||||
_settings.ActiveJobId = job.Id;
|
||||
await RefreshJobAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private Task SelectJobAsync() => Shell.Current.GoToAsync("//jobs");
|
||||
|
||||
[RelayCommand]
|
||||
private Task SelectLocatorAsync() => Shell.Current.GoToAsync($"devicescan?kind={DeviceKind.Locator}");
|
||||
|
||||
[RelayCommand]
|
||||
private Task SelectGpsAsync() => Shell.Current.GoToAsync($"devicescan?kind={DeviceKind.RtkGps}");
|
||||
|
||||
[RelayCommand]
|
||||
private Task ViewMapAsync() => Shell.Current.GoToAsync("//map");
|
||||
|
||||
private void OnFixReceived(object? sender, GnssFix fix)
|
||||
{
|
||||
MainThread.BeginInvokeOnMainThread(() =>
|
||||
{
|
||||
FixStatusLabel = fix.StatusLabel;
|
||||
FixSummary = fix.HasPosition
|
||||
? $"{fix.Latitude:F8}, {fix.Longitude:F8} · {fix.AltitudeCorrected:F2} m"
|
||||
: "No position";
|
||||
FixAccuracy = $"H ±{fix.Hrms:F3} m · V ±{fix.Vrms:F3} m · {fix.SatellitesUsed} sats · batt {fix.BatteryPercent}%";
|
||||
});
|
||||
}
|
||||
|
||||
private void OnPointSaved(object? sender, LoggedPoint point)
|
||||
{
|
||||
RecentPoints.Insert(0, point);
|
||||
while (RecentPoints.Count > 10)
|
||||
RecentPoints.RemoveAt(RecentPoints.Count - 1);
|
||||
PointCount++;
|
||||
}
|
||||
|
||||
private async void OnPacketIgnoredNoJob(object? sender, EventArgs e)
|
||||
{
|
||||
await Shell.Current.DisplayAlert("No Active Job",
|
||||
"A point was logged on the receiver, but no job is active so it was not saved. Create or select a job first.",
|
||||
"OK");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user