2026-05-07 11:39:46 -04:00
|
|
|
|
using TeamsISO.Engine.Controller;
|
|
|
|
|
|
using TeamsISO.Engine.Domain;
|
2026-05-08 00:52:44 -04:00
|
|
|
|
using IsoHealthStats = TeamsISO.Engine.Domain.IsoHealthStats;
|
2026-05-07 11:39:46 -04:00
|
|
|
|
|
|
|
|
|
|
namespace TeamsISO.App.ViewModels;
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Per-row view model for a participant in the participant list.
|
|
|
|
|
|
/// Wraps a domain <see cref="Participant"/> and exposes ISO toggle and naming commands.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public sealed class ParticipantViewModel : ObservableObject
|
|
|
|
|
|
{
|
|
|
|
|
|
private readonly IIsoController _controller;
|
|
|
|
|
|
private Participant _participant;
|
|
|
|
|
|
private bool _isEnabled;
|
|
|
|
|
|
private bool _isProcessing;
|
|
|
|
|
|
private string _customName;
|
|
|
|
|
|
|
|
|
|
|
|
public ParticipantViewModel(IIsoController controller, Participant participant)
|
|
|
|
|
|
{
|
|
|
|
|
|
_controller = controller;
|
|
|
|
|
|
_participant = participant;
|
|
|
|
|
|
_customName = string.Empty;
|
|
|
|
|
|
ToggleIsoCommand = new AsyncRelayCommand(ToggleIsoAsync, () => !_isProcessing);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public Guid Id => _participant.Id;
|
|
|
|
|
|
public string DisplayName => _participant.DisplayName;
|
|
|
|
|
|
public string SourceMachine => _participant.CurrentSource?.MachineName ?? "(disconnected)";
|
|
|
|
|
|
public string SourceFullName => _participant.CurrentSource?.FullName ?? "(disconnected)";
|
|
|
|
|
|
public bool IsOnline => _participant.CurrentSource is not null;
|
|
|
|
|
|
|
|
|
|
|
|
public bool IsEnabled
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isEnabled;
|
|
|
|
|
|
set => SetField(ref _isEnabled, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-08 00:52:44 -04:00
|
|
|
|
private long _framesIn;
|
|
|
|
|
|
private long _framesOut;
|
|
|
|
|
|
private string _incomingResolution = "—";
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Number of frames the receiver has captured so far.</summary>
|
2026-05-08 00:55:57 -04:00
|
|
|
|
public long FramesIn { get => _framesIn; set => SetField(ref _framesIn, value); }
|
2026-05-08 00:52:44 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>Number of frames the sender has emitted so far.</summary>
|
2026-05-08 00:55:57 -04:00
|
|
|
|
public long FramesOut { get => _framesOut; set => SetField(ref _framesOut, value); }
|
2026-05-08 00:52:44 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>Source resolution as "WxH", or em-dash when no frames have been seen yet.</summary>
|
2026-05-08 00:55:57 -04:00
|
|
|
|
public string IncomingResolution { get => _incomingResolution; set => SetField(ref _incomingResolution, value); }
|
2026-05-08 00:52:44 -04:00
|
|
|
|
|
|
|
|
|
|
/// <summary>Updates the live stats display from a controller-side snapshot.</summary>
|
|
|
|
|
|
public void UpdateStats(IsoHealthStats stats)
|
|
|
|
|
|
{
|
|
|
|
|
|
FramesIn = stats.FramesIn;
|
|
|
|
|
|
FramesOut = stats.FramesOut;
|
|
|
|
|
|
IncomingResolution = stats.IncomingWidth > 0 && stats.IncomingHeight > 0
|
|
|
|
|
|
? $"{stats.IncomingWidth}×{stats.IncomingHeight}"
|
|
|
|
|
|
: "—";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-05-07 11:39:46 -04:00
|
|
|
|
public bool IsProcessing
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _isProcessing;
|
|
|
|
|
|
private set
|
|
|
|
|
|
{
|
|
|
|
|
|
if (SetField(ref _isProcessing, value))
|
|
|
|
|
|
ToggleIsoCommand.RaiseCanExecuteChanged();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public string CustomName
|
|
|
|
|
|
{
|
|
|
|
|
|
get => _customName;
|
|
|
|
|
|
set => SetField(ref _customName, value);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public AsyncRelayCommand ToggleIsoCommand { get; }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>Refreshes the underlying participant data (called when the controller emits an updated list).</summary>
|
|
|
|
|
|
public void Update(Participant updated)
|
|
|
|
|
|
{
|
|
|
|
|
|
_participant = updated;
|
|
|
|
|
|
OnPropertyChanged(nameof(DisplayName));
|
|
|
|
|
|
OnPropertyChanged(nameof(SourceMachine));
|
|
|
|
|
|
OnPropertyChanged(nameof(SourceFullName));
|
|
|
|
|
|
OnPropertyChanged(nameof(IsOnline));
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private async Task ToggleIsoAsync()
|
|
|
|
|
|
{
|
|
|
|
|
|
IsProcessing = true;
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsEnabled)
|
|
|
|
|
|
{
|
|
|
|
|
|
await _controller.DisableIsoAsync(Id, CancellationToken.None);
|
|
|
|
|
|
IsEnabled = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
|
|
|
|
|
await _controller.EnableIsoAsync(
|
|
|
|
|
|
Id,
|
|
|
|
|
|
string.IsNullOrWhiteSpace(_customName) ? null : _customName,
|
|
|
|
|
|
CancellationToken.None);
|
|
|
|
|
|
IsEnabled = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
finally
|
|
|
|
|
|
{
|
|
|
|
|
|
IsProcessing = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|