feat(S2-b): connect durable MQTT sync to capture workflow

This commit is contained in:
Brent Perteet
2026-08-20 15:22:29 -05:00
parent a788cebea3
commit 71fcb9b63f
23 changed files with 1680 additions and 22 deletions

View File

@@ -4,6 +4,7 @@ using CommunityToolkit.Mvvm.Input;
using FieldLogger.Models;
using FieldLogger.Services;
using FieldLogger.Services.Data;
using FieldLogger.Services.Sync;
namespace FieldLogger.ViewModels;
@@ -14,6 +15,7 @@ public sealed partial class HomeViewModel : ObservableObject
private readonly PointLogger _pointLogger;
private readonly AppDatabase _database;
private readonly SettingsService _settings;
private readonly IMqttSyncService _sync;
public DeviceConnectionManager Manager => _manager;
@@ -35,20 +37,27 @@ public sealed partial class HomeViewModel : ObservableObject
[ObservableProperty]
private string _fixAccuracy = "";
[ObservableProperty]
private string _syncStatus = "";
public ObservableCollection<LoggedPoint> RecentPoints { get; } = new();
public HomeViewModel(DeviceConnectionManager manager, MaglinkService gps,
PointLogger pointLogger, AppDatabase database, SettingsService settings)
PointLogger pointLogger, AppDatabase database, SettingsService settings, IMqttSyncService sync)
{
_manager = manager;
_gps = gps;
_pointLogger = pointLogger;
_database = database;
_settings = settings;
_sync = sync;
SyncStatus = sync.Status;
_gps.FixReceived += OnFixReceived;
_pointLogger.PointSaved += OnPointSaved;
_pointLogger.PacketIgnoredNoJob += OnPacketIgnoredNoJob;
_sync.StatusChanged += OnSyncStatusChanged;
_sync.PointRejected += OnPointRejected;
}
/// <summary>Called from the page's OnAppearing.</summary>
@@ -146,4 +155,14 @@ public sealed partial class HomeViewModel : ObservableObject
"A point was logged on the receiver, but no job is active so it was not saved. Create or select a job first.",
"OK");
}
private void OnSyncStatusChanged(object? sender, string status) =>
MainThread.BeginInvokeOnMainThread(() => SyncStatus = status);
private void OnPointRejected(object? sender, PointSyncFailureEventArgs e) =>
MainThread.BeginInvokeOnMainThread(async () =>
await Shell.Current.DisplayAlert(
"Point Saved Locally — Sync Needs Attention",
$"Point #{e.Point.Id} remains on this device and was not uploaded. Reason: {e.ReasonCode}.",
"OK"));
}

View File

@@ -2,6 +2,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using FieldLogger.Models;
using FieldLogger.Services;
using FieldLogger.Services.Sync;
namespace FieldLogger.ViewModels;
@@ -9,23 +10,104 @@ public sealed partial class SettingsViewModel : ObservableObject
{
private readonly DeviceConnectionManager _manager;
private readonly SettingsService _settings;
private readonly IMqttSyncService _sync;
public DeviceConnectionManager Manager => _manager;
[ObservableProperty]
private string _mapsApiKey = "";
[ObservableProperty]
private bool _mqttEnabled;
[ObservableProperty]
private string _mqttHost = "";
[ObservableProperty]
private string _mqttPort = "8884";
[ObservableProperty]
private string _mqttOrgId = "";
[ObservableProperty]
private string _mqttPassword = "";
[ObservableProperty]
private string _syncStatus = "";
[ObservableProperty]
private bool _isSavingSync;
public string MqttClientId => _settings.MqttClientId;
public string AppVersion => AppInfo.Current.VersionString;
public SettingsViewModel(DeviceConnectionManager manager, SettingsService settings)
public SettingsViewModel(DeviceConnectionManager manager, SettingsService settings, IMqttSyncService sync)
{
_manager = manager;
_settings = settings;
_sync = sync;
MapsApiKey = settings.GoogleMapsApiKey;
MqttEnabled = settings.MqttEnabled;
MqttHost = settings.MqttHost;
MqttPort = settings.MqttPort.ToString();
MqttOrgId = settings.MqttOrgId;
SyncStatus = sync.Status;
_sync.StatusChanged += OnSyncStatusChanged;
}
partial void OnMapsApiKeyChanged(string value) => _settings.GoogleMapsApiKey = value.Trim();
[RelayCommand]
private async Task SaveSyncAsync()
{
if (!int.TryParse(MqttPort, out var port) || port is < 1 or > 65535)
{
await Shell.Current.DisplayAlert("Invalid MQTT Port", "Enter a port between 1 and 65535.", "OK");
return;
}
if (MqttEnabled && (string.IsNullOrWhiteSpace(MqttHost) || string.IsNullOrWhiteSpace(MqttOrgId)))
{
await Shell.Current.DisplayAlert("Incomplete Sync Settings", "Host and organization id are required.", "OK");
return;
}
IsSavingSync = true;
try
{
_settings.MqttHost = MqttHost;
_settings.MqttPort = port;
_settings.MqttOrgId = MqttOrgId;
_settings.MqttEnabled = MqttEnabled;
if (!string.IsNullOrWhiteSpace(MqttPassword))
{
await _settings.SetMqttPasswordAsync(MqttPassword);
MqttPassword = "";
}
if (MqttEnabled)
{
await _sync.ConnectAsync();
await Shell.Current.DisplayAlert("Sync Connected", "The durable MQTT queue is connected.", "OK");
}
else
{
await _sync.DisconnectAsync();
}
}
catch (Exception ex)
{
await Shell.Current.DisplayAlert("Sync Connection Failed", ex.Message, "OK");
}
finally
{
IsSavingSync = false;
}
}
private void OnSyncStatusChanged(object? sender, string status) =>
MainThread.BeginInvokeOnMainThread(() => SyncStatus = status);
[RelayCommand]
private Task ChangeLocatorAsync() => Shell.Current.GoToAsync($"devicescan?kind={DeviceKind.Locator}");