namespace EnvelopeGenerator.ReceiverUI.Client.Services; /// /// Service für Toast-Benachrichtigungen. Ersetzt AlertifyJS aus dem Web-Projekt. /// /// WARUM ein Service und keine Komponente mit Parametern? /// - Toasts können von ÜBERALL ausgelöst werden (Pages, Services, andere Komponenten) /// - Ein Service ist über Dependency Injection überall verfügbar /// - Die Toast-Komponente im Layout hört auf diesen Service und rendert die Nachrichten /// /// PATTERN: Pub/Sub (Publisher/Subscriber) /// - Publisher: Jede Komponente die _toast.ShowSuccess("...") aufruft /// - Subscriber: Die Toast-Komponente im MainLayout, die auf OnChange hört /// /// Das ist das gleiche Pattern wie beim EnvelopeState. /// public class ToastService { /// /// Liste aller aktuell sichtbaren Toasts. /// Mehrere Toasts können gleichzeitig angezeigt werden (gestapelt). /// public List Messages { get; } = []; /// Event: Informiert die Toast-Komponente über Änderungen public event Action? OnChange; public void ShowSuccess(string text) => Show(text, "success"); public void ShowError(string text) => Show(text, "danger"); public void ShowInfo(string text) => Show(text, "info"); public void ShowWarning(string text) => Show(text, "warning"); /// /// Fügt einen Toast hinzu und entfernt ihn nach der angegebenen Dauer automatisch. /// /// WARUM async void? /// Normalerweise vermeidet man async void. Hier ist es ok, weil: /// - Es ist ein Fire-and-Forget-Timer (wir warten nicht auf das Ergebnis) /// - Fehler im Delay können die App nicht zum Absturz bringen /// - Das ist ein gängiges Pattern für Auto-Dismiss-Logik /// private async void Show(string text, string type, int durationMs = 4000) { var message = new ToastMessage(text, type); Messages.Add(message); OnChange?.Invoke(); // Nach Ablauf der Dauer automatisch entfernen await Task.Delay(durationMs); Messages.Remove(message); OnChange?.Invoke(); } /// Entfernt einen Toast sofort (z.B. wenn der Benutzer auf X klickt) public void Dismiss(ToastMessage message) { Messages.Remove(message); OnChange?.Invoke(); } } /// /// Ein einzelner Toast-Eintrag. /// /// WARUM ein record statt class? /// - Records haben automatisch Equals/GetHashCode basierend auf allen Properties /// - Wir brauchen das für Messages.Remove() — es vergleicht über Referenz-Gleichheit /// - Die Id (Guid) macht jeden Toast einzigartig, auch bei gleichem Text /// public record ToastMessage(string Text, string Type) { /// Eindeutige Id — damit zwei Toasts mit gleichem Text unterscheidbar sind public Guid Id { get; } = Guid.NewGuid(); /// /// Gibt die Bootstrap-Icon-Klasse basierend auf dem Typ zurück. /// success → check-circle, danger → x-circle, etc. /// public string IconClass => Type switch { "success" => "bi-check-circle-fill", "danger" => "bi-x-circle-fill", "warning" => "bi-exclamation-triangle-fill", _ => "bi-info-circle-fill" }; }