using System.Collections.ObjectModel; using System.Reactive.Concurrency; using System.Reactive.Linq; using System.Windows.Threading; using TeamsISO.Engine.Controller; using TeamsISO.Engine.Domain; namespace TeamsISO.App.ViewModels; /// /// Top-level view model for the main window. Owns the live collection of , /// the global settings panel, and the alert banner. Subscribes to 's observables /// and marshals updates onto the UI dispatcher. /// public sealed class MainViewModel : ObservableObject, IDisposable { private readonly IIsoController _controller; private readonly Dispatcher _dispatcher; private readonly IDisposable _participantsSub; private readonly IDisposable _alertsSub; private readonly Dictionary _byId = new(); private string _statusText = "Starting…"; public ObservableCollection Participants { get; } = new(); public GlobalSettingsViewModel Settings { get; } public AlertBannerViewModel AlertBanner { get; } = new(); public string StatusText { get => _statusText; set => SetField(ref _statusText, value); } public MainViewModel(IIsoController controller, Dispatcher dispatcher) { _controller = controller; _dispatcher = dispatcher; Settings = new GlobalSettingsViewModel(controller); _participantsSub = controller.Participants .ObserveOn(new SynchronizationContextScheduler( System.Threading.SynchronizationContext.Current ?? new DispatcherSynchronizationContext(_dispatcher))) .Subscribe(OnParticipantsChanged); _alertsSub = controller.Alerts .ObserveOn(new SynchronizationContextScheduler( System.Threading.SynchronizationContext.Current ?? new DispatcherSynchronizationContext(_dispatcher))) .Subscribe(alert => { AlertBanner.Current = alert; }); } public async Task InitializeAsync(CancellationToken cancellationToken) { StatusText = "Discovering NDI sources…"; await _controller.StartAsync(cancellationToken); StatusText = $"Engine running at {_controller.GlobalSettings.FramerateHz:F2} fps target."; } private void OnParticipantsChanged(IReadOnlyList incoming) { var seenIds = new HashSet(); foreach (var p in incoming) { seenIds.Add(p.Id); if (_byId.TryGetValue(p.Id, out var vm)) { vm.Update(p); } else { vm = new ParticipantViewModel(_controller, p); _byId[p.Id] = vm; Participants.Add(vm); } } // Remove participants no longer present for (var i = Participants.Count - 1; i >= 0; i--) { var vm = Participants[i]; if (!seenIds.Contains(vm.Id)) { _byId.Remove(vm.Id); Participants.RemoveAt(i); } } } public void Dispose() { _participantsSub.Dispose(); _alertsSub.Dispose(); } }