refactor: extract TopBar out of MainWindow.xaml — Phase 2, Commit 14

TopBar.xaml(.cs) takes the Row 0 top controls: logo/About button, REC/ON-AIR
pills + status signs, avatar, Sign In/Start/End buttons. The avatar fetch
(AvatarHttp/_loadedAvatarUrl/LoadAvatarImage/ShowAvatarFallback) moved into the
control with async semantics preserved; the window grabs the control via
FindName in its ctor and calls _topBar.RefreshAvatar() when IsConnected or
AccountAvatarUrl changes (previously OnViewModelPropertyChanged -> LoadAvatarImage).
AccountLight is pure binding (never touched by code-behind) so it moved with the XAML.

Zero behavior change; build 0 warnings; 246 pass, only the 2 known failures.
Also dropped the now-unused System.IO using from MainWindow.xaml.cs.
Docs: ViewModels/index.md Phase 2 tracker updated in the same commit.
This commit is contained in:
2026-08-30 09:13:47 -07:00
parent 691425288f
commit 90f5ad50dd
5 changed files with 197 additions and 165 deletions
+72
View File
@@ -0,0 +1,72 @@
using System;
using System.IO;
using System.Net.Http;
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 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;
}
}