Files
LlamaCasty/Services/LiveBroadcastFormViewModel.cs

201 lines
6.4 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Input;
using ytLive.Helpers;
using ytLive.Models;
namespace ytLive.Services;
/// <summary>
/// Backing VM for the broadcast metadata pull-out (the "Text" tab on the Live
/// screen). Owns the creator-editable liveBroadcast fields: every edit is
/// persisted to the Settings store immediately (pre-load on next run), and the
/// Update command pushes the whole snippet/status set to YouTube via
/// liveBroadcasts.update. The drawer open/close state lives here too so the
/// XAML binds one object.
/// </summary>
public sealed class LiveBroadcastFormViewModel : ViewModelBase
{
private readonly Func<LayoutStore> _store;
private readonly Func<YouTubeStreamService?> _streamService;
private readonly Func<string?> _broadcastId;
private readonly Func<bool> _isLive;
private string _title = string.Empty;
private string _description = string.Empty;
private string _tagsCsv = string.Empty;
private string _visibility = "private";
private bool _madeForKids;
private DateTime? _scheduledStartTime;
private bool _isDrawerOpen;
private bool _isUpdating;
private string? _updateError;
private BroadcastMetadata? _lastSyncedSnapshot;
public LiveBroadcastFormViewModel(
Func<LayoutStore> store,
Func<YouTubeStreamService?> streamService,
Func<string?> broadcastId,
Func<bool>? isLive = null)
{
_store = store;
_streamService = streamService;
_broadcastId = broadcastId;
_isLive = isLive ?? (() => false);
UpdateBroadcastCommand = new RelayCommand(_ => _ = UpdateRemoteAsync(), _ => CanUpdateRemote);
ToggleDrawerCommand = new RelayCommand(_ => IsDrawerOpen = !IsDrawerOpen);
CloseDrawerCommand = new RelayCommand(_ => IsDrawerOpen = false);
}
public ICommand UpdateBroadcastCommand { get; }
public RelayCommand ToggleDrawerCommand { get; }
public RelayCommand CloseDrawerCommand { get; }
public string Title { get => _title; set { if (SetProperty(ref _title, value)) Persist(); } }
public string Description { get => _description; set { if (SetProperty(ref _description, value)) Persist(); } }
public string TagsCsv { get => _tagsCsv; set { if (SetProperty(ref _tagsCsv, value)) Persist(); } }
public bool MadeForKids { get => _madeForKids; set { if (SetProperty(ref _madeForKids, value)) Persist(); } }
public string Visibility
{
get => _visibility;
set { if (SetProperty(ref _visibility, value)) Persist(); }
}
public IReadOnlyList<string> VisibilityOptions { get; } = new[] { "private", "unlisted", "public" };
public DateTime? ScheduledStartTime
{
get => _scheduledStartTime;
private set => SetProperty(ref _scheduledStartTime, value);
}
public string ScheduledStartText => ScheduledStartTime?.ToLocalTime().ToString("g") ?? "set at Go Live";
public bool IsDrawerOpen
{
get => _isDrawerOpen;
set => SetProperty(ref _isDrawerOpen, value);
}
public bool IsUpdating
{
get => _isUpdating;
private set
{
if (SetProperty(ref _isUpdating, value))
OnPropertyChanged(nameof(CanUpdateRemote));
}
}
public string? UpdateError
{
get => _updateError;
private set => SetProperty(ref _updateError, value);
}
public bool CanUpdateRemote => !IsUpdating;
/// <summary>Pull everything from the store (startup / Go Live refresh).
/// Seeds the synced snapshot so the next Update click knows what is already remote.</summary>
public void Load()
{
var meta = _store().LoadBroadcastMetadata();
_title = meta.Title;
_description = meta.Description;
_tagsCsv = meta.TagsCsv;
_visibility = meta.Visibility;
_madeForKids = meta.MadeForKids;
ScheduledStartTime = meta.ScheduledStartTime;
OnPropertyChanged(nameof(Title));
OnPropertyChanged(nameof(Description));
OnPropertyChanged(nameof(TagsCsv));
OnPropertyChanged(nameof(Visibility));
OnPropertyChanged(nameof(MadeForKids));
OnPropertyChanged(nameof(ScheduledStartText));
SnapshotSynced();
}
/// <summary>Called at Go Live with what was actually inserted remotely.</summary>
public void CaptureGoLive(string title, string description, DateTime scheduledStart)
{
Title = title;
Description = description;
ScheduledStartTime = scheduledStart;
UpdateError = null;
SnapshotSynced();
}
public BroadcastMetadata ToMetadata() => new()
{
Title = Title,
Description = Description,
TagsCsv = TagsCsv,
Visibility = Visibility,
MadeForKids = MadeForKids,
ScheduledStartTime = ScheduledStartTime,
};
private void Persist()
{
try
{
_store().SaveBroadcastMetadata(ToMetadata());
}
catch (Exception ex)
{
AppLog.Write($"Broadcast metadata save failed: {ex.Message}");
}
}
private async Task UpdateRemoteAsync()
{
var id = _broadcastId();
var service = _streamService();
if (IsUpdating) return;
IsUpdating = true;
UpdateError = null;
try
{
var current = ToMetadata();
if (!_isLive())
{
UpdateError = null;
IsDrawerOpen = false;
return;
}
if (id == null || service == null)
{
UpdateError = null;
IsDrawerOpen = false;
return;
}
if (_lastSyncedSnapshot != null && current.Equals(_lastSyncedSnapshot))
{
UpdateError = "No changes to update";
return;
}
var error = await service.UpdateBroadcast(id, current);
UpdateError = error;
if (error == null)
{
SnapshotSynced();
IsDrawerOpen = false;
}
}
catch (Exception ex)
{
AppLog.Write($"Broadcast update failed: {ex.Message}");
UpdateError = ex.Message;
}
finally
{
IsUpdating = false;
}
}
private void SnapshotSynced() => _lastSyncedSnapshot = ToMetadata();
}