namespace decePubClient.Services; public enum ToastSeverity { Info, Success, Warning, Error } public sealed record ToastMessage(Guid Id, ToastSeverity Severity, string Text); /// /// The app's snackbar: components add messages (usually through the SUtility extensions AddResponseError, /// AddResponseSuccess, AddWarning, AddException) and Components/ToastHost.razor, placed once in MainLayout, shows them. /// A message already on screen isn't added twice, and at most MaxMessages stay, oldest dropped first. /// public class ToastService { const int MaxMessages = 12; readonly List messages = []; public event Action Changed; public IReadOnlyList Messages => messages; /// Shows a message; with closeAfter it closes itself, otherwise it stays until the user closes it. public void Add(ToastSeverity severity, string text, TimeSpan? closeAfter = null) { if (string.IsNullOrWhiteSpace(text) || messages.Any(m => m.Severity == severity && m.Text == text)) return; var message = new ToastMessage(Guid.NewGuid(), severity, text); messages.Add(message); if (messages.Count > MaxMessages) messages.RemoveAt(0); Changed?.Invoke(); if (closeAfter is { } delay) _ = CloseLater(message.Id, delay); } public void Close(Guid id) { if (messages.RemoveAll(m => m.Id == id) > 0) Changed?.Invoke(); } async Task CloseLater(Guid id, TimeSpan delay, CancellationToken cancellationToken = default) { await Task.Delay(delay, cancellationToken); Close(id); } }