teamsiso/src/TeamsISO.App/ViewModels/ParticipantViewModel.cs

115 lines
3.8 KiB
C#
Raw Normal View History

using TeamsISO.Engine.Controller;
using TeamsISO.Engine.Domain;
using IsoHealthStats = TeamsISO.Engine.Domain.IsoHealthStats;
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);
}
private long _framesIn;
private long _framesOut;
private string _incomingResolution = "—";
/// <summary>Number of frames the receiver has captured so far.</summary>
public long FramesIn { get => _framesIn; private set => SetField(ref _framesIn, value); }
/// <summary>Number of frames the sender has emitted so far.</summary>
public long FramesOut { get => _framesOut; private set => SetField(ref _framesOut, value); }
/// <summary>Source resolution as "WxH", or em-dash when no frames have been seen yet.</summary>
public string IncomingResolution { get => _incomingResolution; private set => SetField(ref _incomingResolution, value); }
/// <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}"
: "—";
}
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;
}
}
}