Two-state flow with go-live dialog, unmissable live indicators, taskbar overlay
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
using System.Windows.Input;
|
||||
using ytLive.Helpers;
|
||||
|
||||
namespace ytLive.ViewModels;
|
||||
|
||||
public class GoLiveViewModel : ViewModelBase
|
||||
{
|
||||
private bool _isSignedIn;
|
||||
private string _accountName = string.Empty;
|
||||
private string _streamTitle = string.Empty;
|
||||
private string _streamDescription = string.Empty;
|
||||
private string _visibility = "Public";
|
||||
|
||||
public bool IsSignedIn
|
||||
{
|
||||
get => _isSignedIn;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isSignedIn, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(ShowSignIn));
|
||||
OnPropertyChanged(nameof(ShowAccount));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool ShowSignIn => !IsSignedIn;
|
||||
public bool ShowAccount => IsSignedIn;
|
||||
|
||||
public string AccountName
|
||||
{
|
||||
get => _accountName;
|
||||
set => SetProperty(ref _accountName, value);
|
||||
}
|
||||
|
||||
public string StreamTitle
|
||||
{
|
||||
get => _streamTitle;
|
||||
set => SetProperty(ref _streamTitle, value);
|
||||
}
|
||||
|
||||
public string StreamDescription
|
||||
{
|
||||
get => _streamDescription;
|
||||
set => SetProperty(ref _streamDescription, value);
|
||||
}
|
||||
|
||||
public string Visibility
|
||||
{
|
||||
get => _visibility;
|
||||
set => SetProperty(ref _visibility, value);
|
||||
}
|
||||
|
||||
public string[] Visibilities { get; } = { "Public", "Unlisted", "Private" };
|
||||
|
||||
public ICommand SignInCommand { get; }
|
||||
public ICommand ChangeAccountCommand { get; }
|
||||
public ICommand StartCommand { get; }
|
||||
public ICommand CancelCommand { get; }
|
||||
|
||||
public GoLiveViewModel()
|
||||
{
|
||||
SignInCommand = new RelayCommand(_ => SignIn());
|
||||
ChangeAccountCommand = new RelayCommand(_ => ChangeAccount());
|
||||
StartCommand = new RelayCommand(_ => StartRequested?.Invoke(), _ => IsSignedIn);
|
||||
CancelCommand = new RelayCommand(_ => CancelRequested?.Invoke());
|
||||
}
|
||||
|
||||
public event Action? StartRequested;
|
||||
public event Action? CancelRequested;
|
||||
|
||||
private void SignIn()
|
||||
{
|
||||
// TODO: OAuth2 flow; for now simulate a successful sign-in
|
||||
AccountName = "Connected Channel";
|
||||
IsSignedIn = true;
|
||||
}
|
||||
|
||||
private void ChangeAccount()
|
||||
{
|
||||
// TODO: re-run OAuth2; for now simulate signing out
|
||||
IsSignedIn = false;
|
||||
AccountName = string.Empty;
|
||||
}
|
||||
}
|
||||
+101
-33
@@ -1,5 +1,7 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Windows;
|
||||
using System.Windows.Input;
|
||||
using System.Windows.Threading;
|
||||
using ytLive.Helpers;
|
||||
using ytLive.Models;
|
||||
using ytLive.Services;
|
||||
@@ -11,13 +13,21 @@ public class MainViewModel : ViewModelBase
|
||||
private readonly YouTubeAuthService _youtubeAuth;
|
||||
private readonly YouTubeStreamService _youtubeStream;
|
||||
private readonly YouTubeChatService _youtubeChat;
|
||||
private readonly DispatcherTimer _liveTimer;
|
||||
|
||||
private Scene? _activeScene;
|
||||
private StreamStatus _streamStatus = StreamStatus.Offline;
|
||||
private StreamHealth _currentHealth = new();
|
||||
private string _streamTitle = string.Empty;
|
||||
private string _streamKey = string.Empty;
|
||||
private bool _isYouTubeConnected;
|
||||
private string _streamDescription = string.Empty;
|
||||
private string _streamVisibility = "Public";
|
||||
private string _windowTitle = "ytLive";
|
||||
private string _topBarBackground = "#16213e";
|
||||
private string _previewGlowBrush = "Transparent";
|
||||
private Thickness _previewGlowThickness = new(0);
|
||||
private string _liveElapsedText = "00:00:00";
|
||||
private double _livePulseOpacity = 1.0;
|
||||
private TimeSpan _liveElapsed;
|
||||
|
||||
public ObservableCollection<Scene> Scenes { get; } = new();
|
||||
public ObservableCollection<ChatMessage> ChatMessages { get; } = new();
|
||||
@@ -37,13 +47,15 @@ public class MainViewModel : ViewModelBase
|
||||
{
|
||||
OnPropertyChanged(nameof(IsOffline));
|
||||
OnPropertyChanged(nameof(IsLive));
|
||||
OnPropertyChanged(nameof(ShowGoLive));
|
||||
OnPropertyChanged(nameof(LiveIndicatorVisible));
|
||||
UpdateLiveVisuals();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsOffline => StreamStatus == StreamStatus.Offline;
|
||||
public bool IsLive => StreamStatus == StreamStatus.Streaming;
|
||||
public bool LiveIndicatorVisible => IsLive;
|
||||
|
||||
public StreamHealth CurrentHealth
|
||||
{
|
||||
@@ -57,33 +69,58 @@ public class MainViewModel : ViewModelBase
|
||||
set => SetProperty(ref _streamTitle, value);
|
||||
}
|
||||
|
||||
public string StreamKey
|
||||
public string StreamDescription
|
||||
{
|
||||
get => _streamKey;
|
||||
set => SetProperty(ref _streamKey, value);
|
||||
get => _streamDescription;
|
||||
set => SetProperty(ref _streamDescription, value);
|
||||
}
|
||||
|
||||
public bool IsYouTubeConnected
|
||||
public string StreamVisibility
|
||||
{
|
||||
get => _isYouTubeConnected;
|
||||
set
|
||||
{
|
||||
if (SetProperty(ref _isYouTubeConnected, value))
|
||||
{
|
||||
OnPropertyChanged(nameof(ShowConnect));
|
||||
OnPropertyChanged(nameof(ShowGoLive));
|
||||
}
|
||||
}
|
||||
get => _streamVisibility;
|
||||
set => SetProperty(ref _streamVisibility, value);
|
||||
}
|
||||
|
||||
public bool ShowConnect => !IsYouTubeConnected;
|
||||
public bool ShowGoLive => IsYouTubeConnected && IsOffline;
|
||||
public string WindowTitle
|
||||
{
|
||||
get => _windowTitle;
|
||||
set => SetProperty(ref _windowTitle, value);
|
||||
}
|
||||
|
||||
public string TopBarBackground
|
||||
{
|
||||
get => _topBarBackground;
|
||||
set => SetProperty(ref _topBarBackground, value);
|
||||
}
|
||||
|
||||
public string PreviewGlowBrush
|
||||
{
|
||||
get => _previewGlowBrush;
|
||||
set => SetProperty(ref _previewGlowBrush, value);
|
||||
}
|
||||
|
||||
public Thickness PreviewGlowThickness
|
||||
{
|
||||
get => _previewGlowThickness;
|
||||
set => SetProperty(ref _previewGlowThickness, value);
|
||||
}
|
||||
|
||||
public string LiveElapsedText
|
||||
{
|
||||
get => _liveElapsedText;
|
||||
set => SetProperty(ref _liveElapsedText, value);
|
||||
}
|
||||
|
||||
public double LivePulseOpacity
|
||||
{
|
||||
get => _livePulseOpacity;
|
||||
set => SetProperty(ref _livePulseOpacity, value);
|
||||
}
|
||||
|
||||
// Commands
|
||||
public ICommand AddSceneCommand { get; }
|
||||
public ICommand RemoveSceneCommand { get; }
|
||||
public ICommand ConnectCommand { get; }
|
||||
public ICommand GoLiveCommand { get; }
|
||||
public ICommand StartStreamCommand { get; }
|
||||
public ICommand EndStreamCommand { get; }
|
||||
|
||||
public MainViewModel()
|
||||
@@ -94,10 +131,12 @@ public class MainViewModel : ViewModelBase
|
||||
|
||||
_youtubeChat.MessageReceived += OnChatMessageReceived;
|
||||
|
||||
_liveTimer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
|
||||
_liveTimer.Tick += OnLiveTimerTick;
|
||||
|
||||
AddSceneCommand = new RelayCommand(_ => AddScene());
|
||||
RemoveSceneCommand = new RelayCommand(scene => RemoveScene(scene as Scene));
|
||||
ConnectCommand = new RelayCommand(_ => ConnectYouTube());
|
||||
GoLiveCommand = new RelayCommand(_ => StartStream(), _ => IsYouTubeConnected && IsOffline);
|
||||
StartStreamCommand = new RelayCommand(_ => BeginGoLive());
|
||||
EndStreamCommand = new RelayCommand(_ => StopStream(), _ => IsLive);
|
||||
|
||||
// Start with a default scene
|
||||
@@ -129,25 +168,54 @@ public class MainViewModel : ViewModelBase
|
||||
});
|
||||
}
|
||||
|
||||
private async void StartStream()
|
||||
private void BeginGoLive()
|
||||
{
|
||||
StreamStatus = StreamStatus.Connecting;
|
||||
|
||||
// TODO: Initialize capture pipeline, encode, and push to RTMP
|
||||
// For now, simulate connection
|
||||
await Task.Delay(500);
|
||||
StreamStatus = StreamStatus.Streaming;
|
||||
var dialog = new GoLiveViewModel();
|
||||
var window = new ytLive.GoLiveWindow(dialog) { Owner = System.Windows.Application.Current.MainWindow };
|
||||
if (window.ShowDialog() == true)
|
||||
{
|
||||
StreamTitle = dialog.StreamTitle;
|
||||
StreamDescription = dialog.StreamDescription;
|
||||
StreamVisibility = dialog.Visibility;
|
||||
WindowTitle = string.IsNullOrWhiteSpace(dialog.StreamTitle)
|
||||
? "ytLive"
|
||||
: $"{dialog.StreamTitle} — ytLive";
|
||||
StreamStatus = StreamStatus.Streaming;
|
||||
}
|
||||
}
|
||||
|
||||
private void StopStream()
|
||||
{
|
||||
StreamStatus = StreamStatus.Offline;
|
||||
WindowTitle = "ytLive";
|
||||
}
|
||||
|
||||
private void ConnectYouTube()
|
||||
private void UpdateLiveVisuals()
|
||||
{
|
||||
// TODO: Launch OAuth2 flow in browser
|
||||
// For now, this is a stub
|
||||
IsYouTubeConnected = false;
|
||||
var live = IsLive;
|
||||
TopBarBackground = live ? "#e94560" : "#16213e";
|
||||
PreviewGlowBrush = live ? "#e94560" : "Transparent";
|
||||
PreviewGlowThickness = live ? new Thickness(3) : new Thickness(0);
|
||||
|
||||
if (live)
|
||||
{
|
||||
_liveElapsed = TimeSpan.Zero;
|
||||
LiveElapsedText = "00:00:00";
|
||||
LivePulseOpacity = 1.0;
|
||||
_liveTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
_liveTimer.Stop();
|
||||
LiveElapsedText = "00:00:00";
|
||||
LivePulseOpacity = 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnLiveTimerTick(object? sender, EventArgs e)
|
||||
{
|
||||
_liveElapsed = _liveElapsed.Add(TimeSpan.FromSeconds(1));
|
||||
LiveElapsedText = _liveElapsed.ToString(@"hh\:mm\:ss");
|
||||
LivePulseOpacity = LivePulseOpacity > 0.5 ? 0.35 : 1.0;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user