e69db4d26c
The bar now renders ONE surface from the FIRST decision the creator makes — Record or Stream — with each world being the whole bar. Creator directive: "I wrote the fucking thing and I still can't figure-out how to do stuff", "forget this one dog plan bullshit". OBS/streaming-tool reference for the one-surface paradigm: https://obsproject.com (single mode toggle + context actions) — cited per spin-guard habit; the go-live/test pattern follows CEV (https://cev-desktop.aSean.xyz) precedent. - Mode: single segmented REC|ON-AIR switch (SegmentToggle/SegmentLabel in Themes/Controls.xaml); radio-exclusive world selection, one tap to flip. - Record world: switch + Start Recording, zero YouTube identity. - Stream world: switch + Go Live + Test + account zone right (Sign In until connected, then avatar with Change Account/Logout context menu). - Test is a child of Stream: procs only stream-armed AND signed-in. - Running: one reality line "dot word elapsed" (REC/LIVE/TEST; green/red/ gold) + End; worlds + switch retire. - Gear moved up from bottom bar, ~3 wordmark letters past the brand, single click opens Settings/Bug/Feature/About menu (TASK 40 Unit B shipped early). - Fix folded in: BeginTestStream now arms OnAirPillOn explicitly (unarmed pump booted with zero encoder outputs -> "At least one output is required" forced-stop cascade from TASK 41's pipeline). - VM: world/reality props + RaiseTopBarModes() wired into StreamStatus, pill, IsRecording, IsTestStream, IsConnected setters. Doc: ai.md top-bar model, ViewModels/index.md, Controls/index.md, TASKS.md, HANDOFF.md, new TASKS/task-42-top-bar-redesign.md; task-40 note. Tests: TopBarModeTests.cs (new Good Dog), PillRadioTests + TestStreamTests gates updated. Build 0 warnings; full suite 314/315 — single abort is the pre-existing env-dependent RealMouseDrag test (Path of Exile 2 running)
85 lines
2.7 KiB
C#
85 lines
2.7 KiB
C#
using System;
|
|
using System.IO;
|
|
using System.Net.Http;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media.Imaging;
|
|
using ytLive.Helpers;
|
|
using ytLive.ViewModels;
|
|
|
|
namespace ytLive.Controls;
|
|
|
|
public partial class TopBar : UserControl
|
|
{
|
|
private static readonly HttpClient AvatarHttp = new() { Timeout = TimeSpan.FromSeconds(15) };
|
|
private string? _loadedAvatarUrl;
|
|
|
|
public TopBar()
|
|
{
|
|
InitializeComponent();
|
|
AvatarImage.Source = null;
|
|
AvatarInitial.Visibility = System.Windows.Visibility.Collapsed;
|
|
}
|
|
|
|
public void RefreshAvatar()
|
|
{
|
|
_ = LoadAvatarImageAsync();
|
|
}
|
|
|
|
private void GearButton_Click(object sender, RoutedEventArgs e)
|
|
{
|
|
// One menu (Settings / Bug / Feature / About), opened on click — moved up
|
|
// from the bottom bar so app-level actions live next to the brand.
|
|
if (sender is Button { ContextMenu: { } menu } button)
|
|
{
|
|
menu.PlacementTarget = button;
|
|
menu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
|
|
menu.IsOpen = true;
|
|
}
|
|
}
|
|
|
|
private async System.Threading.Tasks.Task LoadAvatarImageAsync()
|
|
{
|
|
var vm = DataContext as MainViewModel;
|
|
var url = vm?.AccountAvatarUrl;
|
|
if (vm is not { IsConnected: true })
|
|
{
|
|
ShowAvatarFallback(null);
|
|
return;
|
|
}
|
|
if (string.IsNullOrWhiteSpace(url))
|
|
{
|
|
AppLog.Write("Avatar: connected but no profile image URL on the session");
|
|
ShowAvatarFallback(null);
|
|
return;
|
|
}
|
|
if (url == _loadedAvatarUrl && AvatarImage.Source != null) return;
|
|
try
|
|
{
|
|
var bytes = await AvatarHttp.GetByteArrayAsync(url);
|
|
using var ms = new MemoryStream(bytes);
|
|
var bitmap = new BitmapImage();
|
|
bitmap.BeginInit();
|
|
bitmap.CacheOption = BitmapCacheOption.OnLoad;
|
|
bitmap.StreamSource = ms;
|
|
bitmap.EndInit();
|
|
bitmap.Freeze();
|
|
_loadedAvatarUrl = url;
|
|
AvatarImage.Source = bitmap;
|
|
AvatarInitial.Visibility = System.Windows.Visibility.Collapsed;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
AppLog.Write($"Avatar load failed ({url}): {ex.Message}");
|
|
ShowAvatarFallback(vm?.AccountDisplayName);
|
|
}
|
|
}
|
|
|
|
private void ShowAvatarFallback(string? name)
|
|
{
|
|
_loadedAvatarUrl = null;
|
|
AvatarImage.Source = null;
|
|
AvatarInitial.Text = string.IsNullOrWhiteSpace(name) ? "?" : name.Substring(0, 1).ToUpperInvariant();
|
|
AvatarInitial.Visibility = System.Windows.Visibility.Visible;
|
|
}
|
|
} |