< Summary

Information
Class: MyNet.UI.ViewModels.List.Filtering.FiltersViewModel<T>
Assembly: MyNet.UI
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.UI/ViewModels/List/Filtering/FiltersViewModel.cs
Tag: 323_28699572109
Line coverage
61%
Covered lines: 29
Uncovered lines: 18
Coverable lines: 47
Total lines: 166
Line coverage: 61.7%
Branch coverage
55%
Covered branches: 10
Total branches: 18
Branch coverage: 55.5%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%22100%
set_CurrentFilter(...)100%210%
get_HasActiveFilters()100%210%
set_AutoApply(...)100%11100%
set_IsDirty(...)100%210%
ObserveCollectionChanges(...)100%22100%
Apply()0%620%
Clear()100%210%
Reset()100%210%
HandleFiltersChanged()0%620%
ComputeCurrentFilter()0%620%
Subscribe(...)87.5%88100%
RebuildSubscriptions()100%11100%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.UI/ViewModels/List/Filtering/FiltersViewModel.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="FiltersViewModel.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Collections.Specialized;
 9using System.ComponentModel;
 10using System.Diagnostics.CodeAnalysis;
 11using System.Reactive.Disposables;
 12using System.Reactive.Linq;
 13using MyNet.Observable;
 14using MyNet.Observable.Collections.Filters;
 15using MyNet.Utilities.Deferring;
 16
 17namespace MyNet.UI.ViewModels.List.Filtering;
 18
 19/// <summary>
 20/// Represents the view model for managing filters applied to a collection of items of type T. It maintains a tree of fi
 21/// </summary>
 22/// <typeparam name="T">The type of items to be filtered.</typeparam>
 23public class FiltersViewModel<T> : ObservableObject, IFiltersViewModel<T>
 24{
 25    private readonly DeferredAction _deferredAction;
 26    [SuppressMessage("Usage", "CA2213:Disposable fields should be disposed", Justification = "Disposed through the share
 327    private readonly SerialDisposable _treeSubscriptions = new();
 28
 29    /// <summary>
 30    /// Initializes a new instance of the <see cref="FiltersViewModel{T}"/> class with the specified root filter group v
 31    /// </summary>
 32    /// <param name="root">The root filter group view model.</param>
 33    /// <exception cref="ArgumentNullException">Thrown if the root parameter is null.</exception>
 334    public FiltersViewModel(IFilterGroupViewModel<T> root)
 35    {
 336        Root = root ?? throw new ArgumentNullException(nameof(root));
 37
 338        _deferredAction = new(HandleFiltersChanged);
 339        Disposables.Add(_treeSubscriptions);
 40
 341        RebuildSubscriptions();
 342    }
 43
 44    /// <summary>
 45    /// Gets the root filter group view model, which represents the full filter tree configured in the UI. This property
 46    /// </summary>
 47    public IFilterGroupViewModel<T> Root { get; }
 48
 49    /// <summary>
 50    /// Gets the current filter built from the UI configuration. This property returns an instance of <see cref="IFilter
 51    /// </summary>
 052    public IFilter<T>? CurrentFilter { get; private set => SetProperty(ref field, value); }
 53
 54    /// <summary>
 55    /// Gets a value indicating whether there are any active filters in the current configuration. This property returns
 56    /// </summary>
 057    public bool HasActiveFilters => CurrentFilter is not null;
 58
 59    /// <summary>
 60    /// Gets or sets a value indicating whether the filters should be automatically applied when they change. If set to 
 61    /// </summary>
 362    public bool AutoApply { get; set => SetProperty(ref field, value); } = true;
 63
 64    /// <summary>
 65    /// Gets a value indicating whether the filter configuration has been modified since the last application. This prop
 66    /// </summary>
 067    public bool IsDirty { get; private set => SetProperty(ref field, value); }
 68
 69    /// <summary>
 70    /// Occurs when the filter configuration changes and a new filter is produced. This event is raised whenever the fil
 71    /// </summary>
 72    public event EventHandler<FiltersChangedEventArgs<T>>? FiltersChanged;
 73
 74    /// <summary>
 75    /// Observes collection changes on the specified source collection and returns an observable sequence of collection 
 76    /// </summary>
 77    /// <param name="source">The source collection to observe for changes.</param>
 78    /// <returns>An observable sequence of collection changed event arguments.</returns>
 79    private static IObservable<NotifyCollectionChangedEventArgs> ObserveCollectionChanges(
 80        INotifyCollectionChanged source) =>
 681        System.Reactive.Linq.Observable.FromEventPattern<NotifyCollectionChangedEventHandler, NotifyCollectionChangedEve
 682                h => source.CollectionChanged += h,
 683                h => source.CollectionChanged -= h)
 684            .Select(x => x.EventArgs);
 85
 86    /// <summary>
 87    /// Applies the current filter configuration and raises the FiltersChanged event. This method computes the current f
 88    /// </summary>
 89    public void Apply()
 90    {
 091        CurrentFilter = ComputeCurrentFilter();
 092        IsDirty = false;
 93
 094        FiltersChanged?.Invoke(this, new(CurrentFilter));
 095    }
 96
 97    /// <summary>
 98    /// Clears all filters by setting them to their empty state. This method traverses the filter tree and resets each f
 99    /// </summary>
 100    public void Clear()
 101    {
 0102        using (_deferredAction.Defer())
 0103            Root.Clear();
 0104    }
 105
 106    /// <summary>
 107    /// Resets the filters to their default state. This method traverses the filter tree and resets each filter conditio
 108    /// </summary>
 109    public void Reset()
 110    {
 0111        using (_deferredAction.Defer())
 0112            Root.Reset();
 0113    }
 114
 115    /// <summary>
 116    /// Handles changes to the filter configuration by marking the state as dirty and applying the filters if AutoApply 
 117    /// </summary>
 118    private void HandleFiltersChanged()
 119    {
 0120        IsDirty = true;
 121
 0122        if (AutoApply)
 0123            Apply();
 0124    }
 125
 126    /// <summary>
 127    /// Computes the current filter based on the active conditions defined in the filter tree. This method calls the Bui
 128    /// </summary>
 129    /// <returns>The current filter based on the active conditions, or null if there are no active filters.</returns>
 0130    private ExpressionFilter<T>? ComputeCurrentFilter() => Root.BuildExpression() is { } expr ? new ExpressionFilter<T>(
 131
 132    /// <summary>
 133    /// Subscribes to property changes and collection changes in the filter tree starting from the specified node. This 
 134    /// </summary>
 135    /// <param name="node">The filter node to start subscribing from.</param>
 136    /// <returns>A CompositeDisposable containing all subscriptions for the specified node and its children.</returns>
 137    private CompositeDisposable Subscribe(IFilterNodeViewModel<T> node)
 138    {
 12139        var disposable = new CompositeDisposable();
 12140        if (node is INotifyPropertyChanged npc)
 141        {
 12142            disposable.Add(System.Reactive.Linq.Observable.FromEventPattern<PropertyChangedEventHandler, PropertyChanged
 12143                    h => npc.PropertyChanged += h,
 12144                    h => npc.PropertyChanged -= h)
 12145                .Select(x => x.EventArgs)
 12146                .Subscribe(_ => _deferredAction.Request()));
 147        }
 148
 12149        if (node is IFilterGroupViewModel<T> group)
 150        {
 6151            disposable.Add(ObserveCollectionChanges(group.Children).Subscribe(_ =>
 6152            {
 6153                RebuildSubscriptions();
 6154                _deferredAction.Request();
 6155            }));
 156
 30157            foreach (var child in group.Children)
 9158                disposable.Add(Subscribe(child));
 159        }
 160
 12161        return disposable;
 162    }
 163
 3164    private void RebuildSubscriptions() => _treeSubscriptions.Disposable = Subscribe(Root);
 165}
 166