Creator ruling 2026-11-13: "the app does three things. builds. records. live-streams." So the bar holds three buttons that never disappear; the active one is lit and runs its own flow in place, and the other two dim but stay clickable, so there is no state where the creator cannot see where they are or how to step back. Build [Build Scenes] -> editing on, nothing to start YouTube [Live][Private] -> [Start] -> [Stop] (Private = the private-visibility test stream) Record [Start Recording] -> [● REC hh:mm:ss][Stop Recording] Arming and starting are separate presses on purpose (creator): Start is the last chance to tell someone "check you are ready". Collapsing them would save one click and lose that prompt. This replaces the two-world model (BarWorld) and its output stage outright, including the chips added earlier today. The old pills remain the underlying state (OnAirPillOn/RecordPillOn) so the start, stop and step-down paths are untouched; the activity only decides what the bar shows. Closing the app no longer waits 6 s for a session that cannot stop: it waits ~2 s, then clears the state and closes anyway. A window that refuses to close is worse than a stale flag — and that refusal is what hung the suite in ZoomBoxInteractionTests, which sets IsRecording and closes a real window. Test: TopBarActivityTests pins all three flows, the arm-then-start split, and that stepping back to Build forgets the YouTube choice instead of carrying it. TopBarWorldTests is retired with the model it tested.
118 lines
4.9 KiB
C#
118 lines
4.9 KiB
C#
using System.Windows.Input;
|
|
using ytLive.Helpers;
|
|
|
|
namespace ytLive.ViewModels;
|
|
|
|
/// <summary>What the app is doing (creator, 2026-11-13): build, record, or stream. Three activities,
|
|
/// three buttons, always visible.</summary>
|
|
/// <summary>Which YouTube stream is armed. Choosing does not start it — Start does.</summary>
|
|
public enum StreamMode
|
|
{
|
|
None,
|
|
Live,
|
|
Private,
|
|
}
|
|
|
|
public enum TopActivity
|
|
{
|
|
Build,
|
|
YouTube,
|
|
Record,
|
|
}
|
|
|
|
public partial class MainViewModel
|
|
{
|
|
private TopActivity _activity = TopActivity.Build;
|
|
|
|
public TopActivity Activity => _activity;
|
|
|
|
public bool IsBuildActivity => _activity == TopActivity.Build;
|
|
public bool IsYouTubeActivity => _activity == TopActivity.YouTube;
|
|
public bool IsRecordActivity => _activity == TopActivity.Record;
|
|
|
|
// ─── Each activity's own flow ───
|
|
// Build: nothing to start. YouTube: Live or Private, then Stop. Record: Start, then Stop.
|
|
|
|
// YouTube is two steps ON PURPOSE (creator, 2026-11-13): choosing Live or Private only ARMS it,
|
|
// because Start is the last moment to tell someone "check you are ready". Collapsing the two would
|
|
// trade that prompt for one fewer click.
|
|
private StreamMode _streamMode = StreamMode.None;
|
|
|
|
public bool IsLiveModeChosen => _streamMode == StreamMode.Live;
|
|
public bool IsPrivateModeChosen => _streamMode == StreamMode.Private;
|
|
|
|
public bool ShowYouTubeModeChoices => IsYouTubeActivity && !IsLive && _streamMode == StreamMode.None;
|
|
public bool ShowYouTubeStart => IsYouTubeActivity && !IsLive && _streamMode != StreamMode.None;
|
|
public bool ShowYouTubeStop => IsYouTubeActivity && IsLive;
|
|
public bool ShowRecordStart => IsRecordActivity && !IsRecording;
|
|
public bool ShowRecordStop => IsRecordActivity && IsRecording;
|
|
|
|
/// <summary>The Record activity's own words: its name while idle, its action while chosen.</summary>
|
|
public string RecordActionLabel => IsRecording ? "Stop Recording" : "Start Recording";
|
|
public string RecordElapsedLabel => $"REC: {LiveElapsedText}";
|
|
|
|
public ICommand SelectBuildActivityCommand { get; private set; } = null!;
|
|
public ICommand SelectYouTubeActivityCommand { get; private set; } = null!;
|
|
public ICommand SelectRecordActivityCommand { get; private set; } = null!;
|
|
public ICommand ChooseLiveModeCommand { get; private set; } = null!;
|
|
public ICommand ChoosePrivateModeCommand { get; private set; } = null!;
|
|
public ICommand StartChosenStreamCommand { get; private set; } = null!;
|
|
|
|
private void InitializeActivityCommands()
|
|
{
|
|
SelectBuildActivityCommand = new RelayCommand(_ => SelectActivity(TopActivity.Build));
|
|
SelectYouTubeActivityCommand = new RelayCommand(_ => SelectActivity(TopActivity.YouTube));
|
|
SelectRecordActivityCommand = new RelayCommand(_ => SelectActivity(TopActivity.Record));
|
|
|
|
ChooseLiveModeCommand = new RelayCommand(_ => SetStreamMode(StreamMode.Live), _ => ShowYouTubeModeChoices);
|
|
ChoosePrivateModeCommand = new RelayCommand(_ => SetStreamMode(StreamMode.Private), _ => ShowYouTubeModeChoices);
|
|
|
|
// Start is its own press: the last chance to say "you're about to be live".
|
|
// Signed out still routes to sign-in rather than sitting dead (no-dead-ends rule).
|
|
StartChosenStreamCommand = new RelayCommand(_ =>
|
|
{
|
|
if (!IsConnected) { _ = SignInAsync(); return; }
|
|
if (_streamMode == StreamMode.Private) BeginTestStream();
|
|
else StartSession();
|
|
}, _ => ShowYouTubeStart);
|
|
}
|
|
|
|
/// <summary>Switches activity. Build is the only one that edits; the other two take the layers away
|
|
/// but leave the button lit, so the creator can see where they are and step back.</summary>
|
|
internal void SelectActivity(TopActivity activity)
|
|
{
|
|
_activity = activity;
|
|
OnPropertyChanged(nameof(Activity));
|
|
OnPropertyChanged(nameof(IsBuildActivity));
|
|
OnPropertyChanged(nameof(IsYouTubeActivity));
|
|
OnPropertyChanged(nameof(IsRecordActivity));
|
|
|
|
EditingActive = activity == TopActivity.Build;
|
|
|
|
// The underlying outputs follow the activity, so the existing start commands and the
|
|
// record/live paths agree with what the bar is showing.
|
|
RecordPillOn = activity == TopActivity.Record;
|
|
OnAirPillOn = activity == TopActivity.YouTube;
|
|
_streamMode = StreamMode.None; // the YouTube flow always restarts at the choice
|
|
|
|
RaiseActivityStage();
|
|
}
|
|
|
|
private void SetStreamMode(StreamMode mode)
|
|
{
|
|
_streamMode = mode;
|
|
OnPropertyChanged(nameof(IsLiveModeChosen));
|
|
OnPropertyChanged(nameof(IsPrivateModeChosen));
|
|
RaiseActivityStage();
|
|
}
|
|
|
|
internal void RaiseActivityStage()
|
|
{
|
|
OnPropertyChanged(nameof(ShowYouTubeModeChoices));
|
|
OnPropertyChanged(nameof(ShowYouTubeStart));
|
|
OnPropertyChanged(nameof(ShowYouTubeStop));
|
|
OnPropertyChanged(nameof(ShowRecordStart));
|
|
OnPropertyChanged(nameof(ShowRecordStop));
|
|
}
|
|
}
|