< Summary

Information
Class: MyNet.UI.Toasting.ToastManager
Assembly: MyNet.UI
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.UI/Toasting/ToastManager.cs
Tag: 323_28699572109
Line coverage
72%
Covered lines: 62
Uncovered lines: 24
Coverable lines: 86
Total lines: 223
Line coverage: 72%
Branch coverage
72%
Covered branches: 29
Total branches: 40
Branch coverage: 72.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%22100%
OnNotification(...)66.66%7675%
Add(...)100%11100%
Enqueue(...)75%44100%
StartLifetime(...)62.5%22840%
HookLifecycle(...)100%22100%
UnhookLifecycle(...)66.66%7675%
Remove(...)100%11100%
RemoveCore(...)50%4471.42%
TryDequeue()100%44100%
Clear()100%210%
Dispose()75%4488.88%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.UI/Toasting/ToastManager.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="ToastManager.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Generic;
 9using System.Collections.ObjectModel;
 10using System.Linq;
 11using System.Reactive.Concurrency;
 12using System.Reactive.Linq;
 13using MyNet.UI.Notifications;
 14using MyNet.UI.Notifications.Models;
 15using MyNet.UI.Threading;
 16using MyNet.UI.Toasting.Filters;
 17using MyNet.UI.Toasting.Models;
 18using MyNet.UI.Toasting.Settings;
 19
 20namespace MyNet.UI.Toasting;
 21
 22/// <summary>
 23/// Manages the display and lifecycle of toasts based on incoming notifications, applying filtering, prioritization, and
 24/// </summary>
 25public sealed class ToastManager : IToastManager
 26{
 1227    private readonly ObservableCollection<IToast> _visible = [];
 1228    private readonly PriorityQueue<INotification, int> _queue = new();
 1229    private readonly Dictionary<Guid, IDisposable> _lifetimeSubscriptions = [];
 1230    private readonly Dictionary<Guid, EventHandler<CloseRequestedEventArgs>> _closeHandlers = [];
 31    private readonly ISchedulerProvider _scheduler;
 32    private readonly IToastFactory _factory;
 33    private readonly IToastFilter _filter;
 34    private readonly ToastManagerOptions _options;
 35    private readonly IDisposable _subscription;
 36    private bool _isDisposed;
 37
 38    /// <summary>
 39    /// Initializes a new instance of the <see cref="ToastManager"/> class, subscribing to the notification service to r
 40    /// </summary>
 41    /// <param name="service">The notification service to subscribe to for receiving notifications.</param>
 42    /// <param name="scheduler">The scheduler provider for managing UI and background thread operations.</param>
 43    /// <param name="factory">The factory used to create toast instances.</param>
 44    /// <param name="filter">The filter used to determine which notifications should be displayed as toasts.</param>
 45    /// <param name="options">The options for configuring the behavior of the toast manager.</param>
 46    public ToastManager(
 47        INotificationService service,
 48        ISchedulerProvider scheduler,
 49        IToastFactory factory,
 50        IToastFilter filter,
 51        ToastManagerOptions? options = null)
 52    {
 1253        _scheduler = scheduler;
 1254        _factory = factory;
 1255        _filter = filter;
 1256        _options = options ?? new ToastManagerOptions();
 57
 1258        Toasts = new(_visible);
 59
 1260        _subscription = service.Notifications
 1261            .ObserveOn(_scheduler.Ui)
 1262            .Subscribe(OnNotification);
 1263    }
 64
 65    /// <inheritdoc />
 66    public ReadOnlyObservableCollection<IToast> Toasts { get; }
 67
 68    /// <summary>
 69    /// Handles incoming notifications from the service, applying the filter to determine if they should be displayed as
 70    /// </summary>
 71    /// <param name="notification">The notification to process for potential display as a toast.</param>
 72    private void OnNotification(INotification notification)
 73    {
 1574        if (_isDisposed)
 075            return;
 76
 1577        if (!_filter.ShouldDisplay(notification))
 078            return;
 79
 1580        if (_visible.Count < _options.MaxVisibleToasts)
 81        {
 982            Add(notification);
 83        }
 84        else
 85        {
 686            Enqueue(notification);
 87        }
 688    }
 89
 90    /// <summary>
 91    /// Creates and displays a toast for the given notification, adding it to the collection of visible toasts and start
 92    /// </summary>
 93    /// <param name="notification">The notification to display as a toast.</param>
 94    private void Add(INotification notification)
 95    {
 1296        var toast = _factory.Create(notification);
 97
 1298        _visible.Add(toast);
 99
 12100        HookLifecycle(toast);
 12101        StartLifetime(toast);
 12102    }
 103
 104    /// <summary>
 105    /// Enqueues a notification that cannot be displayed immediately due to the maximum visible toasts limit, using the 
 106    /// </summary>
 107    /// <param name="notification">The notification to enqueue for later display as a toast.</param>
 108    private void Enqueue(INotification notification)
 109    {
 6110        if (_options.MaxQueueSize <= 0 || _queue.Count >= _options.MaxQueueSize)
 3111            return;
 112
 3113        var priority = _options.PrioritySelector(notification);
 3114        _queue.Enqueue(notification, -priority);
 3115    }
 116
 117    /// <summary>
 118    /// Starts the lifetime management for a toast that is set to auto-close, using a timer to automatically remove the 
 119    /// </summary>
 120    /// <param name="toast">The toast for which to start lifetime management.</param>
 121    private void StartLifetime(IToast toast)
 122    {
 12123        if (toast.Settings.ClosingStrategy is not ToastClosingStrategy.AutoClose and not ToastClosingStrategy.Both)
 9124            return;
 125
 3126        if (toast.Settings.FreezeOnMouseEnter)
 3127            return;
 128
 0129        var duration = toast.Settings.Duration ?? _options.DefaultDuration;
 130
 0131        var subscription = System.Reactive.Linq.Observable.Timer(duration, _scheduler.Background)
 0132            .ObserveOn(_scheduler.Ui)
 0133            .Subscribe(_ => Remove(toast));
 134
 0135        _lifetimeSubscriptions[toast.Notification.Id] = subscription;
 0136    }
 137
 138    private void HookLifecycle(IToast toast)
 139    {
 12140        if (toast.Notification is not IClosableNotification closableNotification)
 9141            return;
 142
 3143        _closeHandlers[toast.Notification.Id] = handler;
 3144        closableNotification.CloseRequested += handler;
 3145        return;
 146
 147        void handler(object? sender, CloseRequestedEventArgs e) => Remove(toast);
 148    }
 149
 150    private void UnhookLifecycle(IToast toast)
 151    {
 12152        if (toast.Notification is IClosableNotification closableNotification
 12153            && _closeHandlers.TryGetValue(toast.Notification.Id, out var handler))
 154        {
 3155            closableNotification.CloseRequested -= handler;
 3156            _closeHandlers.Remove(toast.Notification.Id);
 157        }
 158
 12159        if (_lifetimeSubscriptions.TryGetValue(toast.Notification.Id, out var subscription))
 160        {
 0161            subscription.Dispose();
 0162            _lifetimeSubscriptions.Remove(toast.Notification.Id);
 163        }
 12164    }
 165
 166    /// <inheritdoc />
 9167    public void Remove(IToast toast) => _scheduler.Ui.Schedule(() => RemoveCore(toast));
 168
 169    private void RemoveCore(IToast toast)
 170    {
 9171        if (_isDisposed)
 0172            return;
 173
 9174        if (!_visible.Remove(toast))
 0175            return;
 176
 9177        UnhookLifecycle(toast);
 9178        TryDequeue();
 9179    }
 180
 181    /// <summary>
 182    /// Attempts to dequeue the next notification from the priority queue and display it as a toast if there is capacity
 183    /// </summary>
 184    private void TryDequeue()
 185    {
 12186        while (_visible.Count < _options.MaxVisibleToasts && _queue.Count > 0)
 187        {
 3188            var next = _queue.Dequeue();
 3189            Add(next);
 190        }
 9191    }
 192
 193    /// <inheritdoc />
 194    public void Clear() =>
 0195        _scheduler.Ui.Schedule(() =>
 0196        {
 0197            if (_isDisposed)
 0198                return;
 0199
 0200            foreach (var toast in _visible.ToList())
 0201                UnhookLifecycle(toast);
 0202
 0203            _visible.Clear();
 0204            _queue.Clear();
 0205        });
 206
 207    /// <inheritdoc />
 208    public void Dispose()
 209    {
 12210        if (_isDisposed)
 0211            return;
 212
 12213        _isDisposed = true;
 12214        _subscription.Dispose();
 215
 30216        foreach (var toast in _visible.ToList())
 3217            UnhookLifecycle(toast);
 218
 12219        _visible.Clear();
 12220        _queue.Clear();
 12221    }
 222}
 223