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:
brentperteet
2026-07-06 13:46:50 -05:00
commit b602b762c9
79 changed files with 5807 additions and 0 deletions

View File

@@ -0,0 +1,104 @@
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 JobListItem : ObservableObject
{
public required Job Job { get; init; }
[ObservableProperty]
private int _pointCount;
[ObservableProperty]
private bool _isActive;
public string Name => Job.Name;
public string CreatedLabel => Job.CreatedUtc.ToLocalTime().ToString("g");
}
public sealed partial class JobsViewModel : ObservableObject
{
private readonly AppDatabase _database;
private readonly SettingsService _settings;
public ObservableCollection<JobListItem> Jobs { get; } = new();
[ObservableProperty]
private bool _isBusy;
public JobsViewModel(AppDatabase database, SettingsService settings)
{
_database = database;
_settings = settings;
}
public async Task RefreshAsync()
{
IsBusy = true;
try
{
var jobs = await _database.GetJobsAsync();
var activeId = _settings.ActiveJobId;
Jobs.Clear();
foreach (var job in jobs)
{
Jobs.Add(new JobListItem
{
Job = job,
PointCount = await _database.GetPointCountAsync(job.Id),
IsActive = job.Id == activeId,
});
}
}
finally
{
IsBusy = false;
}
}
[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 RefreshAsync();
}
[RelayCommand]
private async Task SetActiveAsync(JobListItem item)
{
_settings.ActiveJobId = item.Job.Id;
foreach (var j in Jobs)
j.IsActive = j.Job.Id == item.Job.Id;
await Task.CompletedTask;
}
[RelayCommand]
private Task OpenAsync(JobListItem item)
=> Shell.Current.GoToAsync($"jobdetail?jobId={item.Job.Id}");
[RelayCommand]
private async Task DeleteAsync(JobListItem item)
{
var confirmed = await Shell.Current.DisplayAlert("Delete Job",
$"Delete \"{item.Job.Name}\" and its {item.PointCount} logged points?", "Delete", "Cancel");
if (!confirmed)
return;
await _database.DeleteJobAsync(item.Job.Id);
if (_settings.ActiveJobId == item.Job.Id)
_settings.ActiveJobId = null;
await RefreshAsync();
}
}