Files
HC_APTBS/ViewModels/ResultDisplayViewModel.cs
LucianoDev 37d099cdbd feat: add Ford VP44 unlock progress dialog, K-Line fast unlock, localization, safety dialogs, and settings
Unlock progress UI:
- UnlockProgressDialog with dark-themed progress ring, phase indicator, elapsed
  time, and cancel/close buttons (non-modal, draggable borderless window)
- UnlockProgressViewModel with event-driven progress tracking via IUnlockService
- Triggers on pump selection (manual or K-Line auto-detect), not test start

UnlockService rewrite:
- Persistent CAN senders that outlive the unlock sequence (StopSenders on pump change)
- Concurrent K-Line fast unlock: awaits session Connected, sends RAM timer shortcut
  ({02 88 02 03 A8 01 00}), verifies via CAN TestUnlock before skipping wait
- Fix Type 1 verification (Value == 0 means unlocked, was inverted)

K-Line fast unlock support:
- IKwpService.TryFastUnlockAsync / KwpService implementation

Additional features:
- ILocalizationService with ES/EN resource dictionaries and runtime switching
- Safety dialogs: VoltageWarning, OilPumpConfirm, RpmSafetyWarning
- SettingsDialog for app configuration
- BenchService enhancements, ConfigurationService improvements, PDF report updates
- All UI strings localized via DynamicResource

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-16 13:22:48 +02:00

154 lines
6.1 KiB
C#

using System.Collections.Generic;
using System.Collections.ObjectModel;
using CommunityToolkit.Mvvm.ComponentModel;
using HC_APTBS.Models;
using HC_APTBS.Services;
namespace HC_APTBS.ViewModels
{
/// <summary>
/// A single result row shown in the results grid for one receive parameter.
/// </summary>
public sealed partial class ResultRowViewModel : ObservableObject
{
private readonly ILocalizationService _loc;
/// <summary>Initialises a new result row with a localization service.</summary>
public ResultRowViewModel(ILocalizationService loc) => _loc = loc;
[ObservableProperty] private string _phaseName = string.Empty;
[ObservableProperty] private string _parameterName = string.Empty;
[ObservableProperty] private double _target;
[ObservableProperty] private double _tolerance;
[ObservableProperty] private double _average;
[ObservableProperty] private bool _passed;
/// <summary>Localised "PASS" or "FAIL" label.</summary>
public string ResultLabel => Passed ? _loc.GetString("Common.Pass") : _loc.GetString("Common.Fail");
}
/// <summary>
/// ViewModel for the ResultDisplay user control.
///
/// <para>
/// Shows a flat table of all phase/parameter combinations with their
/// measured average, target, tolerance, and pass/fail result.
/// </para>
/// </summary>
public sealed partial class ResultDisplayViewModel : ObservableObject
{
private readonly ILocalizationService _loc;
/// <summary>Initialises a new result display with a localization service.</summary>
public ResultDisplayViewModel(ILocalizationService loc) => _loc = loc;
// ── Properties ────────────────────────────────────────────────────────────
/// <summary>Name of the test whose results are displayed.</summary>
[ObservableProperty] private string _testName = string.Empty;
/// <summary>Overall pass/fail for all displayed results.</summary>
[ObservableProperty] private bool _overallPassed;
/// <summary>All result rows.</summary>
public ObservableCollection<ResultRowViewModel> Results { get; } = new();
// ── Public API ────────────────────────────────────────────────────────────
/// <summary>
/// Populates the results table from a completed <see cref="TestDefinition"/>.
/// </summary>
public void LoadResults(TestDefinition test)
{
TestName = test.Name;
Results.Clear();
bool allPassed = true;
foreach (var phase in test.Phases)
{
if (!phase.Enabled || phase.Receives == null) continue;
foreach (var tp in phase.Receives)
{
if (tp.Result == null) continue;
allPassed = allPassed && tp.Result.Passed;
Results.Add(new ResultRowViewModel(_loc)
{
PhaseName = phase.Name,
ParameterName = tp.Name,
Target = tp.Value,
Tolerance = tp.Tolerance,
Average = tp.Result.Average,
Passed = tp.Result.Passed
});
}
}
OverallPassed = allPassed && Results.Count > 0;
}
/// <summary>Adds or updates a live measurement row during test execution.</summary>
public void UpdateLiveValue(string phaseName, string paramName, double currentValue,
double target, double tolerance)
{
foreach (var row in Results)
{
if (row.PhaseName == phaseName && row.ParameterName == paramName)
{
row.Average = currentValue;
return;
}
}
Results.Add(new ResultRowViewModel(_loc)
{
PhaseName = phaseName,
ParameterName = paramName,
Target = target,
Tolerance = tolerance,
Average = currentValue
});
}
/// <summary>
/// Populates the results table from all completed tests in the pump's test list.
/// Clears existing results first, then appends rows from every test that has results.
/// </summary>
/// <param name="tests">All test definitions for the current pump.</param>
public void LoadAllResults(IReadOnlyList<TestDefinition> tests)
{
Results.Clear();
bool allPassed = true;
foreach (var test in tests)
{
foreach (var phase in test.Phases)
{
if (!phase.Enabled || phase.Receives == null) continue;
foreach (var tp in phase.Receives)
{
if (tp.Result == null) continue;
allPassed = allPassed && tp.Result.Passed;
Results.Add(new ResultRowViewModel(_loc)
{
PhaseName = phase.Name,
ParameterName = tp.Name,
Target = tp.Value,
Tolerance = tp.Tolerance,
Average = tp.Result.Average,
Passed = tp.Result.Passed
});
}
}
}
TestName = tests.Count > 0 ? _loc.GetString("Result.AllTests") : string.Empty;
OverallPassed = allPassed && Results.Count > 0;
}
/// <summary>Clears all results.</summary>
public void Clear()
{
Results.Clear();
OverallPassed = false;
}
}
}