Files
LlamaCasty/ViewModels/MainViewModel.Ypp.cs
T
gramps 70fcf89f9e feat(topbar): the recording lives in the pill — green pulse, REC clock, Stop Recording
Build 730 (creator):

- Left pill while recording: solid #2ECC71 dot pulsing 1.0 <-> 0.2 on the existing one-second timer, and
  "REC: hh:mm:ss" in Consolas so the digits cannot jitter the pill's width. Idle face unchanged.
- Right pill while recording: a stop square and "Stop Recording"; clicking it runs the normal stop, which
  halts and resets the clock and returns the pair to "Local MP4" / "Start Recording".
- The separate reality line (dot + REC/LIVE + elapsed + End) is now LIVE-ONLY: a recording would otherwise
  print the same three facts twice. RunningVisible is IsLive, and ShowOutputStage no longer hides itself
  for a recording (it must be on screen to carry the clock and the stop).

Two rules collided and are now separated rather than merged: a creator's Stop on a recording restores the
Local MP4 pair (build 730), while a pipeline FAILURE still clears both pills (the 2026-09-01 zombie rule).
StopStream(afterFailure:) carries the difference, and the pump-failure path passes it.

RealAppHost.Run was rethrowing with `throw failure;`, which destroyed the original stack — a failing
assertion inside the action reported the wrapper's line instead of its own. ExceptionDispatchInfo now
preserves it; that is how the two failures above were located in one pass instead of five.

680 pass (one audio-pipe instance flake under full-suite load, passes alone), 0 warnings.
Scope justification: Assets/starting-background.jpg is outside this unit — the creator's replacement art,
still awaiting their visual check.
2026-11-12 16:52:36 -08:00

70 lines
2.9 KiB
C#

using ytLive.Services;
namespace ytLive.ViewModels;
public partial class MainViewModel
{
/// <summary>Test seam (mirrors CameraEnumeratorOverride): tests inject a fake
/// stats service before constructing the VM so refresh never hits the network.</summary>
internal static Func<ChannelStatsService>? ChannelStatsFactoryOverride { get; set; }
/// <summary>The YPP pull-out backing VM — the drawer below Stream Settings
/// on the Live screen's right edge (TASK 39).</summary>
public Services.YppTrackerViewModel Ypp { get; private set; } = null!;
/// <summary>The Test Stream pull-out backing VM — the TEST drawer at the
/// bottom of the right rail (TASK 41): mock chat input (real insert →
/// poll round-trip) + locally-simulated member / SuperChat / subscriber rows.</summary>
public Services.TestSessionViewModel TestSession { get; private set; } = null!;
private void InitYpp()
{
Ypp = new YppTrackerViewModel(
() => _layoutStore,
() => ChannelStatsFactoryOverride?.Invoke() ?? new ChannelStatsService(_youtubeAuth),
() => IsConnected,
() => _youtubeAuth.CurrentChannel?.DisplayName,
rewardEvents: () => new SuperChatEventService(_youtubeAuth),
channelId: () => _youtubeAuth.CurrentChannel?.ChannelId,
sessionStartedAtUtc: () => _sessionStartedAtUtc,
watchMetrics: () => new YouTubeAnalyticsService(_youtubeAuth));
TestSession = new TestSessionViewModel(
_youtubeStream,
_youtubeChat,
onEndStream: () => StopStream());
// One drawer at a time: the three pull-outs share the preview's right edge,
// so opening one collapses the other two (all directions).
BroadcastForm.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(LiveBroadcastFormViewModel.IsDrawerOpen) && BroadcastForm.IsDrawerOpen)
{
Ypp.IsDrawerOpen = false;
TestSession.IsDrawerOpen = false;
CloseSocialsDrawer();
CloseSettingsDrawerIfOpen();
}
};
Ypp.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(YppTrackerViewModel.IsDrawerOpen) && Ypp.IsDrawerOpen)
{
BroadcastForm.IsDrawerOpen = false;
TestSession.IsDrawerOpen = false;
CloseSocialsDrawer();
CloseSettingsDrawerIfOpen();
}
};
TestSession.PropertyChanged += (_, e) =>
{
if (e.PropertyName == nameof(TestSessionViewModel.IsDrawerOpen) && TestSession.IsDrawerOpen)
{
BroadcastForm.IsDrawerOpen = false;
Ypp.IsDrawerOpen = false;
CloseSocialsDrawer();
CloseSettingsDrawerIfOpen();
}
};
}
}