feat: complete UM Trace iOS and field UI integration
This commit is contained in:
@@ -40,41 +40,112 @@ public sealed partial class JobsViewModel : ObservableObject
|
||||
{
|
||||
private readonly AppDatabase _database;
|
||||
private readonly SettingsService _settings;
|
||||
private readonly SemaphoreSlim _refreshGate = new(1, 1);
|
||||
|
||||
public ObservableCollection<JobListItem> Jobs { get; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isBusy;
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isRefreshing;
|
||||
|
||||
public JobsViewModel(AppDatabase database, SettingsService settings)
|
||||
{
|
||||
_database = database;
|
||||
_settings = settings;
|
||||
}
|
||||
|
||||
public Task LoadAsync()
|
||||
=> ReloadAsync(showRefreshIndicator: false, skipIfBusy: true);
|
||||
|
||||
[RelayCommand]
|
||||
public async Task RefreshAsync()
|
||||
private Task RefreshAsync()
|
||||
=> ReloadAsync(showRefreshIndicator: true, skipIfBusy: false);
|
||||
|
||||
private async Task ReloadAsync(bool showRefreshIndicator, bool skipIfBusy)
|
||||
{
|
||||
IsBusy = true;
|
||||
var entered = skipIfBusy
|
||||
? await _refreshGate.WaitAsync(0)
|
||||
: await WaitForRefreshGateAsync();
|
||||
if (!entered)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
IsBusy = true;
|
||||
if (showRefreshIndicator)
|
||||
IsRefreshing = true;
|
||||
|
||||
var jobs = await _database.GetJobsAsync();
|
||||
var activeId = _settings.ActiveJobId;
|
||||
|
||||
Jobs.Clear();
|
||||
// Build a snapshot off-screen, then reconcile it with the existing observable
|
||||
// collection. Keeping the existing rows prevents CollectionView from repeatedly
|
||||
// discarding its measured cells and jumping back to the top.
|
||||
var items = new List<JobListItem>(jobs.Count);
|
||||
foreach (var job in jobs)
|
||||
{
|
||||
Jobs.Add(new JobListItem
|
||||
items.Add(new JobListItem
|
||||
{
|
||||
Job = job,
|
||||
PointCount = await _database.GetPointCountAsync(job.Id),
|
||||
IsActive = job.Id == activeId,
|
||||
});
|
||||
}
|
||||
|
||||
ApplySnapshot(items);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (showRefreshIndicator)
|
||||
IsRefreshing = false;
|
||||
IsBusy = false;
|
||||
_refreshGate.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> WaitForRefreshGateAsync()
|
||||
{
|
||||
await _refreshGate.WaitAsync();
|
||||
return true;
|
||||
}
|
||||
|
||||
private void ApplySnapshot(IReadOnlyList<JobListItem> incoming)
|
||||
{
|
||||
var incomingIds = incoming.Select(item => item.Job.Id).ToHashSet();
|
||||
|
||||
for (var index = Jobs.Count - 1; index >= 0; index--)
|
||||
{
|
||||
if (!incomingIds.Contains(Jobs[index].Job.Id))
|
||||
Jobs.RemoveAt(index);
|
||||
}
|
||||
|
||||
for (var targetIndex = 0; targetIndex < incoming.Count; targetIndex++)
|
||||
{
|
||||
var replacement = incoming[targetIndex];
|
||||
var existingIndex = -1;
|
||||
for (var index = targetIndex; index < Jobs.Count; index++)
|
||||
{
|
||||
if (Jobs[index].Job.Id == replacement.Job.Id)
|
||||
{
|
||||
existingIndex = index;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingIndex < 0)
|
||||
{
|
||||
Jobs.Insert(targetIndex, replacement);
|
||||
continue;
|
||||
}
|
||||
|
||||
var existing = Jobs[existingIndex];
|
||||
existing.PointCount = replacement.PointCount;
|
||||
existing.IsActive = replacement.IsActive;
|
||||
|
||||
if (existingIndex != targetIndex)
|
||||
Jobs.Move(existingIndex, targetIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -101,7 +172,7 @@ public sealed partial class JobsViewModel : ObservableObject
|
||||
_settings.ActiveJobId = job.Id;
|
||||
Console.WriteLine($"NewJobAsync: Set active job to {job.Id}");
|
||||
|
||||
await RefreshAsync();
|
||||
await ReloadAsync(showRefreshIndicator: false, skipIfBusy: false);
|
||||
Console.WriteLine("NewJobAsync: Refresh complete");
|
||||
}
|
||||
catch (Exception ex)
|
||||
@@ -135,6 +206,6 @@ public sealed partial class JobsViewModel : ObservableObject
|
||||
await _database.DeleteJobAsync(item.Job.Id);
|
||||
if (_settings.ActiveJobId == item.Job.Id)
|
||||
_settings.ActiveJobId = null;
|
||||
await RefreshAsync();
|
||||
await ReloadAsync(showRefreshIndicator: false, skipIfBusy: false);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user