Toasts: non-blocking notifications replace MessageBoxes + log-only failures (TASK 24)

This commit is contained in:
2026-08-23 12:25:04 -07:00
parent 809c066184
commit b400ab59c5
12 changed files with 369 additions and 50 deletions
+23 -13
View File
@@ -96,6 +96,10 @@ public class MainViewModel : ViewModelBase
private DispatcherTimer? _saveDebounce;
private bool _isLoading;
// Toasts (TASK 24): non-blocking user-facing messages. Parameterless ctor
// news up the real service; the VM is built from XAML so no DI seam exists.
private readonly INotificationService _notifications = new NotificationService();
// Webcam: one camera input app-wide. The identity (DeviceId) lives on the
// Webcam entity; each scene that shows the webcam has a WebcamSceneConfig
// (webcam.{scene}.config). CameraManager still owns the single session.
@@ -1593,6 +1597,8 @@ public class MainViewModel : ViewModelBase
{
TokenStore.Clear();
AppLog.Write("Saved session could not be refreshed; signed out");
_notifications.Info("Signed out of YouTube",
"The saved session expired — sign in again when you next go live.");
return;
}
}
@@ -1656,6 +1662,8 @@ public class MainViewModel : ViewModelBase
_layoutStore.SaveIsPremium(false);
_layoutStore.SaveLicenseValidatedAt(DateTime.UtcNow);
AppLog.Write($"License validation failed: {result.Error}");
_notifications.Warning("Premium is no longer active",
$"{result.Error} — watermark branding returns until the license is renewed.");
}
}
catch (Exception ex)
@@ -2594,9 +2602,8 @@ public class MainViewModel : ViewModelBase
var started = await _cameraManager.AcquireAsync(_webcam.DeviceId);
if (!started)
{
MessageBox.Show(
WebcamError ?? "Couldn't start that camera. It may be in use by another app, or Windows camera access may be turned off.",
"LlamaCasty", MessageBoxButton.OK, MessageBoxImage.Warning);
_notifications.Warning("Camera unavailable",
WebcamError ?? "Couldn't start that camera. It may be in use by another app, or Windows camera access may be turned off.");
}
}
@@ -2618,9 +2625,8 @@ public class MainViewModel : ViewModelBase
var started = await _cameraManager.AcquireAsync(device.Id);
if (!started)
{
MessageBox.Show(
WebcamError ?? "Couldn't start that camera. It may be in use by another app, or Windows camera access may be turned off.",
"LlamaCasty", MessageBoxButton.OK, MessageBoxImage.Warning);
_notifications.Warning("Camera unavailable",
WebcamError ?? "Couldn't start that camera. It may be in use by another app, or Windows camera access may be turned off.");
break;
}
}
@@ -2707,7 +2713,7 @@ public class MainViewModel : ViewModelBase
return candidates;
}
private static byte[]? PickImageBytes(string title)
private byte[]? PickImageBytes(string title)
{
var dialog = new OpenFileDialog
{
@@ -2721,8 +2727,7 @@ public class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
MessageBox.Show($"Couldn't read that image: {ex.Message}", "LlamaCasty",
MessageBoxButton.OK, MessageBoxImage.Warning);
_notifications.Warning("Couldn't read that image", ex.Message);
return null;
}
}
@@ -2889,8 +2894,7 @@ public class MainViewModel : ViewModelBase
var channel = await Task.Run(() => _youtubeAuth.AuthenticateAsync());
if (channel == null)
{
MessageBox.Show("Sign-in was unsuccessful. Please try again.", "LlamaCasty",
MessageBoxButton.OK, MessageBoxImage.Warning);
_notifications.Warning("Sign-in was unsuccessful", "Please try again.");
return null;
}
@@ -2900,8 +2904,7 @@ public class MainViewModel : ViewModelBase
}
catch (Exception ex)
{
MessageBox.Show($"Sign-in failed: {ex.Message}", "LlamaCasty",
MessageBoxButton.OK, MessageBoxImage.Error);
_notifications.Error("Sign-in failed", ex.Message);
return null;
}
}
@@ -2985,6 +2988,8 @@ public class MainViewModel : ViewModelBase
if (stream == null)
{
AppLog.Write("Reusable stream unavailable; check the OAuth session");
_notifications.Error("Couldn't go live",
"The reusable stream is unavailable — check the YouTube sign-in and try again.");
StreamStatus = StreamStatus.Error;
return;
}
@@ -2997,6 +3002,7 @@ public class MainViewModel : ViewModelBase
if (_currentBroadcastId == null)
{
AppLog.Write("Broadcast creation failed; check the OAuth session");
_notifications.Error("Couldn't go live", "YouTube rejected the broadcast — check the sign-in and try again.");
StreamStatus = StreamStatus.Error;
return;
}
@@ -3026,6 +3032,7 @@ public class MainViewModel : ViewModelBase
catch (Exception ex)
{
AppLog.Write($"Go-live preparation failed: {ex.Message}");
_notifications.Error("Couldn't go live", ex.Message);
StreamStatus = StreamStatus.Error;
return;
}
@@ -3159,6 +3166,8 @@ public class MainViewModel : ViewModelBase
{
MicStatus = MicStatus.NotConnected;
AppLog.Write("Mic: no capture devices found — mic capture not started");
_notifications.Warning("No microphone found",
"Mic capture wasn't started — plug one in and pick it from the mixer.");
return;
}
_audioMixer.Start();
@@ -3333,6 +3342,7 @@ public class MainViewModel : ViewModelBase
private void OnFramePumpFailed(object? sender, string message)
{
AppLog.Write($"Frame pump failed: {message}");
_notifications.Error("The stream pipeline stopped", message);
if (IsLive) StreamStatus = StreamStatus.Error;
}