using TeamsISO.Engine.Controller; using TeamsISO.Engine.Domain; using IsoHealthStats = TeamsISO.Engine.Domain.IsoHealthStats; namespace TeamsISO.App.ViewModels; /// /// Per-row view model for a participant in the participant list. /// Wraps a domain and exposes ISO toggle and naming commands. /// 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 = "—"; /// Number of frames the receiver has captured so far. public long FramesIn { get => _framesIn; set => SetField(ref _framesIn, value); } /// Number of frames the sender has emitted so far. public long FramesOut { get => _framesOut; set => SetField(ref _framesOut, value); } /// Source resolution as "WxH", or em-dash when no frames have been seen yet. public string IncomingResolution { get => _incomingResolution; set => SetField(ref _incomingResolution, value); } /// Updates the live stats display from a controller-side snapshot. 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; } /// Refreshes the underlying participant data (called when the controller emits an updated list). 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; } } }