using CommunityToolkit.Mvvm.ComponentModel; using Microsoft.Extensions.DependencyInjection; using System.Collections; using System.Collections.Specialized; using System.Reactive.Disposables; namespace Toolkit.Foundation; public partial class ObservableCollection : ObservableObject, IObservableCollectionViewModel, IInitializer, IActivated, IDeactivating, IDeactivated, IDeactivatable, IList, IList, IReadOnlyList, INotifyCollectionChanged, ICollectionSynchronization, IServiceProviderRequired, IServiceFactoryRequired, IMediatorRequired, IPublisherRequired, IDisposerRequired, INotificationHandler>, INotificationHandler>, INotificationHandler>, INotificationHandler>, INotificationHandler>, INotificationHandler>, INotificationHandler>, INotificationHandler> where TItem : notnull, IDisposable { private readonly System.Collections.ObjectModel.ObservableCollection collection = []; private readonly IDispatcher dispatcher; private readonly Queue pendingEvents = []; private readonly Dictionary trackedProperties = []; [ObservableProperty] private bool activated; private bool clearing; [ObservableProperty] private int count; [ObservableProperty] private bool initialized; [ObservableProperty] private TItem? selectedItem; public ObservableCollection(IServiceProvider provider, IServiceFactory factory, IMediator mediator, IPublisher publisher, ISubscription subscriber, IDisposer disposer) { Provider = provider; Factory = factory; Mediator = mediator; Publisher = publisher; Disposer = disposer; subscriber.Add(this); dispatcher = Provider.GetRequiredService(); collection.CollectionChanged += OnCollectionChanged; } public ObservableCollection(IServiceProvider provider, IServiceFactory factory, IMediator mediator, IPublisher publisher, ISubscription subscriber, IDisposer disposer, IEnumerable items) { Provider = provider; Factory = factory; Mediator = mediator; Publisher = publisher; Disposer = disposer; subscriber.Add(this); dispatcher = Provider.GetRequiredService(); collection.CollectionChanged += OnCollectionChanged; AddRange(items); } public event NotifyCollectionChangedEventHandler? CollectionChanged; public event EventHandler? DeactivateHandler; public IDisposer Disposer { get; private set; } public IServiceFactory Factory { get; private set; } bool IList.IsFixedSize => false; bool ICollection.IsReadOnly => false; bool IList.IsReadOnly => false; bool ICollection.IsSynchronized => false; public IMediator Mediator { get; } public IServiceProvider Provider { get; private set; } public IPublisher Publisher { get; private set; } object ICollection.SyncRoot => this; public TItem this[int index] { get => collection[index]; set => SetItem(index, value); } object? IList.this[int index] { get => collection[index]; set { TItem? item = default; try { item = (TItem)value!; } catch (InvalidCastException) { } this[index] = item!; } } public TItem Add(params object?[] parameters) where T : TItem { T? item = Factory.Create(parameters); Add(item); return item; } public void Add(TItem item) { int index = collection.Count; InsertItem(index, item); UpdateSelection(item); } public void Add(object item) { int index = collection.Count; InsertItem(index, (TItem)item); } int IList.Add(object? value) { TItem? item = default; try { item = (TItem)value!; } catch (InvalidCastException) { } Add(item!); return Count - 1; } public void AddRange(IEnumerable items) { foreach (TItem? item in items) { Add(item); } } public void Clear() { clearing = true; foreach (TItem item in this.ToList()) { Disposer.Dispose(item); Disposer.Remove(this, item); } ClearItems(); clearing = false; } public void Commit() { foreach (object trackedProperty in trackedProperties.Values) { ((dynamic)trackedProperty).Commit(); } } public bool Contains(TItem item) => collection.Contains(item); bool IList.Contains(object? value) => IsCompatibleObject(value) && Contains((TItem)value!); public void CopyTo(TItem[] array, int index) => collection.CopyTo(array, index); void ICollection.CopyTo(Array array, int index) => collection.CopyTo((TItem[])array, index); public Task Deactivate() { DeactivateHandler?.Invoke(this, new EventArgs()); return Task.CompletedTask; } public virtual void Dispose() { GC.SuppressFinalize(this); Disposer.Dispose(this); } public void Fetch(bool reset = false) { if (reset) { Clear(); } SynchronizeExpression expression = BuildAggregateExpression(); Publisher.PublishUI(expression.Value, expression.Key); } public void Fetch(Func aggregateDelegate, bool reset = false) { if (reset) { Clear(); } SynchronizeExpression expression = aggregateDelegate.Invoke(); Publisher.PublishUI(expression.Value, expression.Key); } public IEnumerator GetEnumerator() => collection.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)collection).GetEnumerator(); public Task Handle(RemoveEventArgs args) { if (Activated) { foreach (TItem item in this.ToList()) { if (args.Value is not null && args.Value.Equals(item)) { Remove(item); } } } else { pendingEvents.Enqueue(args); } return Task.CompletedTask; } public Task Handle(CreateEventArgs args) { if (Activated) { if (args.Value is TItem item) { Add(item); } } else { pendingEvents.Enqueue(args); } return Task.CompletedTask; } public Task Handle(InsertEventArgs args) { if (Activated) { if (args.Value is TItem item) { Insert(args.Index, item); } } else { pendingEvents.Enqueue(args); } return Task.CompletedTask; } public Task Handle(MoveToEventArgs args) { if (Activated) { Move(args.OldIndex, args.NewIndex); } else { pendingEvents.Enqueue(args); } return Task.CompletedTask; } public Task Handle(MoveEventArgs args) { if (Activated) { if (args.Value is TItem item) { Move(args.Index, item); } } else { pendingEvents.Enqueue(args); } return Task.CompletedTask; } public Task Handle(ReplaceEventArgs args) { if (Activated) { if (args.Value is TItem item) { Replace(args.Index, item); } } else { pendingEvents.Enqueue(args); } return Task.CompletedTask; } public Task Handle(RemoveAtEventArgs args) { if (Activated) { int index = args.Index; if (index >= 0 && index <= Count - 1) { RemoveAt(index); } } else { pendingEvents.Enqueue(args); } return Task.CompletedTask; } public Task Handle(SelectionEventArgs args) => Task.CompletedTask; public int IndexOf(TItem item) => collection.IndexOf(item); int IList.IndexOf(object? value) => IsCompatibleObject(value) ? IndexOf((TItem)value!) : -1; public Task Initialize() { if (Initialized) { return Task.CompletedTask; } Initialized = true; Fetch(); return Task.CompletedTask; } public TItem Insert(int index = 0, params object?[] parameters) where T : TItem { T? item = Factory.Create(parameters); InsertItem(index, item); UpdateSelection(item); return item; } public void Insert(int index, TItem item) { InsertItem(index, item); UpdateSelection(item); } void IList.Insert(int index, object? value) { if (value is TItem item) { Insert(index, item); UpdateSelection(item); } } public bool Move(int oldIndex, int newIndex) { if (oldIndex < 0) { return false; } TItem item = this[oldIndex]; bool moveSelection = false; if (item is ISelectable oldSelection) { if (oldSelection.Selected) { moveSelection = true; SelectedItem = default; } } RemoveItem(oldIndex); InsertItem(newIndex, item); if (moveSelection) { if (item is ISelectable newSelection) { newSelection.Selected = true; dispatcher.Invoke(() => SelectedItem = item); } } return true; } public bool Move(int index, TItem item) { int oldIndex = collection.IndexOf(item); if (oldIndex < 0) { return false; } RemoveItem(oldIndex); Insert(index, item); return true; } public virtual Task OnActivated() { Activated = true; while (pendingEvents.Count > 0) { object current = pendingEvents.Dequeue(); Handle((dynamic)current); } return Task.CompletedTask; } public virtual Task OnDeactivated() { Activated = false; return Task.CompletedTask; } public virtual Task OnDeactivating() => Task.CompletedTask; public bool Remove(TItem item) { int index = collection.IndexOf(item); if (index < 0) { return false; } Disposer.Dispose(item); Disposer.Remove(this, item); RemoveItem(index); if (item.Equals(SelectedItem)) { int newIndex = Math.Min(index, Count - 1); TItem? selectedItem = newIndex >= 0 ? this[newIndex] : default; dispatcher.Invoke(() => SelectedItem = selectedItem); } return true; } void IList.Remove(object? value) { if (IsCompatibleObject(value)) { Remove((TItem)value!); } } public void RemoveAt(int index) => RemoveItem(index); public bool Replace(int index, TItem item) { if (index <= Count - 1) { RemoveItem(index); } else { index = Count; } Insert(index, item); return true; } public void Revert() { foreach (object trackedProperty in trackedProperties.Values) { ((dynamic)trackedProperty).Revert(); } } public void Track(string propertyName, Func getter, Action setter) { if (!trackedProperties.ContainsKey(propertyName)) { T initialValue = getter(); trackedProperties[propertyName] = new TrackedProperty(initialValue, setter, getter); } } protected virtual SynchronizeExpression BuildAggregateExpression() => new(new SynchronizeEventArgs()); protected virtual void ClearItems() => collection.Clear(); protected virtual void InsertItem(int index, TItem item) { Disposer.Add(this, item); Disposer.Add(item, Disposable.Create(() => { if (item is IRemovable && !clearing) { if (item is IList collection) { collection.Clear(); } Remove(item); } })); collection.Insert(index > Count ? Count : index, item); } protected virtual void RemoveItem(int index) => collection.RemoveAt(index); protected virtual void SetItem(int index, TItem item) => collection[index] = item; private static bool IsCompatibleObject(object? value) => (value is TItem) || (value == null && default(TItem) == null); private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) { Count = collection.Count; CollectionChanged?.Invoke(this, args); } partial void OnSelectedItemChanged(TItem? oldValue, TItem? newValue) { if (oldValue is ISelectable oldSelection) { oldSelection.Selected = false; } if (newValue is ISelectable newSelection) { newSelection.Selected = true; } } private void UpdateSelection(TItem item) { if (item is ISelectable newSelection) { if (newSelection.Selected) { if (SelectedItem is ISelectable oldSelection) { oldSelection.Selected = false; } dispatcher.Invoke(() => SelectedItem = item); } } } } public partial class ObservableCollection(IServiceProvider provider, IServiceFactory factory, IMediator mediator, IPublisher publisher, ISubscription subscriber, IDisposer disposer, TValue value) : ObservableCollection(provider, factory, mediator, publisher, subscriber, disposer) where TViewModel : notnull, IDisposable where TValue : notnull { [ObservableProperty] private TValue value = value; protected virtual void OnValueChanged() { } partial void OnValueChanged(TValue value) => OnValueChanged(); } public partial class ObservableCollection : ObservableCollection where TViewModel : notnull, IDisposable where TKey : notnull where TValue : notnull { [ObservableProperty] private TKey key; [ObservableProperty] private TValue value; public ObservableCollection(IServiceProvider provider, IServiceFactory factory, IMediator mediator, IPublisher publisher, ISubscription subscriber, IDisposer disposer, TKey key, TValue value) : base(provider, factory, mediator, publisher, subscriber, disposer) { Key = key; Value = value; } public ObservableCollection(IServiceProvider provider, IServiceFactory factory, IMediator mediator, IPublisher publisher, ISubscription subscriber, IDisposer disposer, IEnumerable items, TKey key, TValue value) : base(provider, factory, mediator, publisher, subscriber, disposer, items) { Key = key; Value = value; } protected virtual void OnValueChanged() { } partial void OnValueChanged(TValue value) => OnValueChanged(); } public class ObservableCollection : ObservableCollection { public ObservableCollection(IServiceProvider provider, IServiceFactory factory, IMediator mediator, IPublisher publisher, ISubscription subscriber, IDisposer disposer) : base(provider, factory, mediator, publisher, subscriber, disposer) { } public ObservableCollection(IServiceProvider provider, IServiceFactory factory, IMediator mediator, IPublisher publisher, ISubscription subscriber, IDisposer disposer, IEnumerable items) : base(provider, factory, mediator, publisher, subscriber, disposer, items) { } }