feat(ui): Show Desktop toggle, avatar login, chat preview, backdrop seeding, border animations

- Source.ShowDesktop bool persisted in DB (schema migration + LayoutStore)
- ClearBackdropCaptureAsync clears CaptureKey + VideoImageSource for static fallback
- Top bar: red/green/pulsing status light, avatar via BitmapImage, Log In button
- ShowStartStream now requires IsConnected
- RunMockChatPreviewAsync: simple async loop, ChatPreviewEnabled property on Source
- EnsureDefaultBackdrop called on every scene switch, seeds all 5 scenes
- SeedEndingBackdrop replaces existing, SeedChatBackdrop added
- SceneCatalog: Chat scene display name corrected (layer = 'YouTube Chat')
- Left panel spacing consistency, context menu reordered
- Border animation rendering via code-behind Storyboard approach
- 3 new backdrop assets (ending-backdrop.jpg, chat-backdrop.jpg, live-backdrop.jpg)
- Build 0 warnings, 178 tests passing
This commit is contained in:
2026-08-20 18:02:41 -07:00
parent a76a3d2acd
commit 0bd17ab428
18 changed files with 1022 additions and 216 deletions
+20
View File
@@ -1,5 +1,7 @@
using System;
using System.Globalization;
using System.Linq;
using System.Windows.Data;
namespace ytLive.Models;
@@ -19,6 +21,24 @@ public static class SceneCatalog
public static readonly string[] All = { Starting, Live, Brb, Chat, Ending };
private static readonly Dictionary<string, string> DisplayNames = new(StringComparer.OrdinalIgnoreCase)
{
};
public static string DisplayName(string? name)
=> name != null && DisplayNames.TryGetValue(name.Trim(), out var display) ? display : name ?? string.Empty;
public static readonly IValueConverter DisplayNameConverter = new SceneDisplayNameConverter();
private sealed class SceneDisplayNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> DisplayName(value as string);
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> value ?? string.Empty;
}
public static bool IsCanonical(string? name)
=> name != null && All.Contains(name.Trim(), StringComparer.OrdinalIgnoreCase);