feat(ui): add MainViewModel with live participants collection and dispatcher marshalling
Some checks failed
CI / build-and-test (push) Failing after 34s
Some checks failed
CI / build-and-test (push) Failing after 34s
This commit is contained in:
parent
fbb73bcf04
commit
8c441318d8
1 changed files with 96 additions and 0 deletions
96
src/TeamsISO.App/ViewModels/MainViewModel.cs
Normal file
96
src/TeamsISO.App/ViewModels/MainViewModel.cs
Normal file
|
|
@ -0,0 +1,96 @@
|
|||
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;
|
||||
|
||||
/// <summary>
|
||||
/// Top-level view model for the main window. Owns the live collection of <see cref="ParticipantViewModel"/>,
|
||||
/// the global settings panel, and the alert banner. Subscribes to <see cref="IIsoController"/>'s observables
|
||||
/// and marshals updates onto the UI dispatcher.
|
||||
/// </summary>
|
||||
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<Guid, ParticipantViewModel> _byId = new();
|
||||
private string _statusText = "Starting…";
|
||||
|
||||
public ObservableCollection<ParticipantViewModel> 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<Participant> incoming)
|
||||
{
|
||||
var seenIds = new HashSet<Guid>();
|
||||
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();
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue