Gear cleanup + Default Location for Recordings + single Start + socials hover glow
KISS: removed Gear-menu Save/SaveAs/Open Layout (redundant with auto-save; multi-layout = OBS). App Settings: first real setting — Default Location for Recordings (Browse + Use Downloads reset, fallback: configured → Downloads → MyVideos). Single Start button (pills = intent, button = trigger). Socials button moved to preview bottom row, left of TRAX; hover pulses the SocialBarElement DropShadowEffect (sine wave BlurRadius 24→48, Opacity 0.7→1.0 at 30ms). TextBox OneWay binding fix for get-only RecordFolderDisplay. ONE fallback test. Build 0 warnings, 247 tests (2 known).
This commit is contained in:
+28
-54
@@ -84,6 +84,10 @@ public class MainViewModel : ViewModelBase
|
||||
private bool _onAirPillOn;
|
||||
private bool _isRecording;
|
||||
private string? _recordFolder;
|
||||
|
||||
/// <summary>Resolved record folder — the configured choice, or Downloads (0th case).</summary>
|
||||
public string RecordFolderDisplay => _recordFolder ?? DefaultRecordFolder();
|
||||
|
||||
private string? _activeRecordPath;
|
||||
private DateTime _recordStartTime;
|
||||
private TimeSpan _recordLength;
|
||||
@@ -519,8 +523,6 @@ public class MainViewModel : ViewModelBase
|
||||
OnPropertyChanged(nameof(RecDotOpacity));
|
||||
OnPropertyChanged(nameof(OnAirBrush));
|
||||
OnPropertyChanged(nameof(IsLivePrivate));
|
||||
OnPropertyChanged(nameof(IsStreamingStart));
|
||||
OnPropertyChanged(nameof(PrimaryButtonText));
|
||||
OnPropertyChanged(nameof(ShowPrimaryStartButton));
|
||||
OnPropertyChanged(nameof(ShowLogInButton));
|
||||
OnPropertyChanged(nameof(ShowEndStreamButton));
|
||||
@@ -545,8 +547,6 @@ public class MainViewModel : ViewModelBase
|
||||
if (!value) OnAirPillOn = false;
|
||||
OnPropertyChanged(nameof(CanToggleOnAir));
|
||||
OnPropertyChanged(nameof(ShowPrimaryStartButton));
|
||||
OnPropertyChanged(nameof(PrimaryButtonText));
|
||||
OnPropertyChanged(nameof(IsStreamingStart));
|
||||
OnPropertyChanged(nameof(AccountStatusLightToolTip));
|
||||
}
|
||||
}
|
||||
@@ -1004,7 +1004,6 @@ public class MainViewModel : ViewModelBase
|
||||
set
|
||||
{
|
||||
if (!SetProperty(ref _recordPillOn, value)) return;
|
||||
OnPropertyChanged(nameof(PrimaryButtonText));
|
||||
OnPropertyChanged(nameof(ShowPrimaryStartButton));
|
||||
OnPropertyChanged(nameof(CanStartSession));
|
||||
}
|
||||
@@ -1017,7 +1016,6 @@ public class MainViewModel : ViewModelBase
|
||||
{
|
||||
if (value && !CanToggleOnAir) return; // needs a sign-in
|
||||
if (!SetProperty(ref _onAirPillOn, value)) return;
|
||||
OnPropertyChanged(nameof(PrimaryButtonText));
|
||||
OnPropertyChanged(nameof(ShowPrimaryStartButton));
|
||||
OnPropertyChanged(nameof(CanStartSession));
|
||||
}
|
||||
@@ -1045,12 +1043,6 @@ public class MainViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>"Start Streaming" unless ONLY the record pill is on → "Start Recording".</summary>
|
||||
public string PrimaryButtonText => IsStreamingStart ? "Start Streaming" : "Start Recording";
|
||||
|
||||
/// <summary>Streaming (not record-only) — the ON-AIR pill is on OR we're already live.</summary>
|
||||
public bool IsStreamingStart => OnAirPillOn || IsLive;
|
||||
|
||||
/// <summary>The primary button shows when there's something to start, but never
|
||||
/// while a session is running — once recording/live the ONLY control is the End
|
||||
/// button.</summary>
|
||||
@@ -1613,6 +1605,7 @@ public class MainViewModel : ViewModelBase
|
||||
public ICommand StartStreamCommand { get; }
|
||||
public ICommand EndStreamCommand { get; }
|
||||
public ICommand ChooseRecordFolderCommand { get; }
|
||||
public ICommand ResetRecordFolderCommand { get; }
|
||||
public ICommand OpenSettingsCommand { get; }
|
||||
public ICommand OpenBugCommand { get; }
|
||||
public ICommand OpenFeatureCommand { get; }
|
||||
@@ -1625,9 +1618,6 @@ public class MainViewModel : ViewModelBase
|
||||
public ICommand OpenCoffeeCommand { get; }
|
||||
public ICommand OpenPremiumCommand { get; }
|
||||
public ICommand OpenLicensesCommand { get; }
|
||||
public ICommand SaveLayoutCommand { get; }
|
||||
public ICommand SaveLayoutAsCommand { get; }
|
||||
public ICommand OpenLayoutCommand { get; }
|
||||
public ICommand ActivateLicenseCommand { get; }
|
||||
public ICommand OpenLicenseEntryCommand { get; }
|
||||
public ICommand OpenHotkeyConfigCommand { get; }
|
||||
@@ -1702,9 +1692,7 @@ public class MainViewModel : ViewModelBase
|
||||
StartStreamCommand = new RelayCommand(_ => StartSession());
|
||||
EndStreamCommand = new RelayCommand(_ => StopStream(), _ => IsLive || IsRecording);
|
||||
ChooseRecordFolderCommand = new RelayCommand(_ => ChooseRecordFolder());
|
||||
SaveLayoutCommand = new RelayCommand(_ => SaveLayoutNow());
|
||||
SaveLayoutAsCommand = new RelayCommand(_ => SaveLayoutAs());
|
||||
OpenLayoutCommand = new RelayCommand(_ => OpenLayoutFile());
|
||||
ResetRecordFolderCommand = new RelayCommand(_ => ResetRecordFolder());
|
||||
ActivateLicenseCommand = new RelayCommand(_ => _ = ActivateLicenseAsync(), _ => !IsLicenseValidating);
|
||||
OpenLicenseEntryCommand = new RelayCommand(_ => IsLicenseEntryOpen = !IsLicenseEntryOpen);
|
||||
OpenHotkeyConfigCommand = new RelayCommand(_ => OpenHotkeyConfig());
|
||||
@@ -2439,38 +2427,6 @@ public class MainViewModel : ViewModelBase
|
||||
}
|
||||
}
|
||||
|
||||
private void SaveLayoutAs()
|
||||
{
|
||||
var dialog = new SaveFileDialog
|
||||
{
|
||||
Title = "Save Layout As",
|
||||
Filter = "LlamaCasty layout (*.yll)|*.yll|All files (*.*)|*.*",
|
||||
DefaultExt = ".yll",
|
||||
FileName = "my-layout.yll",
|
||||
};
|
||||
if (dialog.ShowDialog() != true) return;
|
||||
|
||||
_layoutStore.Dispose();
|
||||
_layoutStore = new LayoutStore(dialog.FileName);
|
||||
_activeLayoutPath = dialog.FileName;
|
||||
SaveLayoutNow();
|
||||
}
|
||||
|
||||
private void OpenLayoutFile()
|
||||
{
|
||||
var dialog = new OpenFileDialog
|
||||
{
|
||||
Title = "Open Layout",
|
||||
Filter = "LlamaCasty layout (*.yll;*.db)|*.yll;*.db|All files (*.*)|*.*",
|
||||
};
|
||||
if (dialog.ShowDialog() != true) return;
|
||||
|
||||
_layoutStore.Dispose();
|
||||
_layoutStore = new LayoutStore(dialog.FileName);
|
||||
_activeLayoutPath = dialog.FileName;
|
||||
LoadLayout();
|
||||
}
|
||||
|
||||
// ─── Auto-save wiring ───
|
||||
private void OnScenesChanged(object? sender, NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
@@ -3195,8 +3151,7 @@ public class MainViewModel : ViewModelBase
|
||||
{
|
||||
try
|
||||
{
|
||||
var folder = _recordFolder ?? Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "ytLlive", "recordings");
|
||||
var folder = _recordFolder ?? DefaultRecordFolder();
|
||||
Directory.CreateDirectory(folder);
|
||||
var name = RecordingFile.BuildStartName(DateTime.Now) + ".mp4";
|
||||
var path = Path.Combine(folder, name);
|
||||
@@ -3422,17 +3377,36 @@ public class MainViewModel : ViewModelBase
|
||||
var dialog = new Microsoft.Win32.OpenFolderDialog
|
||||
{
|
||||
Title = "Choose a folder for local recordings",
|
||||
InitialDirectory = _recordFolder ??
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.MyVideos),
|
||||
InitialDirectory = _recordFolder ?? DefaultRecordFolder(),
|
||||
};
|
||||
if (dialog.ShowDialog() == true)
|
||||
{
|
||||
_recordFolder = dialog.FolderName;
|
||||
_layoutStore.SaveRecordFolder(_recordFolder);
|
||||
OnPropertyChanged(nameof(RecordFolderDisplay));
|
||||
_notifications.Info("Record folder set", _recordFolder);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetRecordFolder()
|
||||
{
|
||||
_recordFolder = null;
|
||||
_layoutStore.SaveRecordFolder(null);
|
||||
OnPropertyChanged(nameof(RecordFolderDisplay));
|
||||
_notifications.Info("Record folder reset", RecordFolderDisplay);
|
||||
}
|
||||
|
||||
/// <summary>Falls back to the user's Downloads folder when no preference is set;
|
||||
/// returns MyVideos if Downloads doesn't exist.</summary>
|
||||
internal static string DefaultRecordFolder()
|
||||
{
|
||||
var downloads = Path.Combine(
|
||||
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile), "Downloads");
|
||||
return Directory.Exists(downloads)
|
||||
? downloads
|
||||
: Environment.GetFolderPath(Environment.SpecialFolder.MyVideos);
|
||||
}
|
||||
|
||||
private void OnMicLevelChanged(float level)
|
||||
{
|
||||
// NAudio raises on its capture thread; marshal to the UI thread so the
|
||||
|
||||
Reference in New Issue
Block a user