diff --git a/Hyperbar.Windows.MediaController/MediaInformationViewModel.cs b/Hyperbar.Windows.MediaController/MediaInformationViewModel.cs index e7f2ad2..2752bf7 100644 --- a/Hyperbar.Windows.MediaController/MediaInformationViewModel.cs +++ b/Hyperbar.Windows.MediaController/MediaInformationViewModel.cs @@ -11,7 +11,10 @@ public partial class MediaInformationViewModel : [ObservableProperty] private string description = "this is a test description"; - public MediaInformationViewModel(IServiceFactory serviceFactory, IMediator mediator, IDisposer disposer, ITemplateFactory templateFactory) : base(serviceFactory, mediator, disposer, templateFactory) + public MediaInformationViewModel(IServiceFactory serviceFactory, + IMediator mediator, + IDisposer disposer, + ITemplateFactory templateFactory) : base(serviceFactory, mediator, disposer, templateFactory) { } } diff --git a/Hyperbar.Windows.Primary/ConfigurationChangedHandler.cs b/Hyperbar.Windows.Primary/ConfigurationChangedHandler.cs index fa1fc89..08fbc18 100644 --- a/Hyperbar.Windows.Primary/ConfigurationChangedHandler.cs +++ b/Hyperbar.Windows.Primary/ConfigurationChangedHandler.cs @@ -1,11 +1,35 @@ namespace Hyperbar.Windows.Primary; -public class ConfigurationChangedHandler : +public class ConfigurationChangedHandler(IMediator mediator, + PrimaryWidgetConfiguration configuration, + IViewModelFactory factory, + IViewModelCache cache) : INotificationHandler> { - public ValueTask Handle(ConfigurationChanged notification, + public async ValueTask Handle(ConfigurationChanged notification, CancellationToken cancellationToken) { - throw new NotImplementedException(); - } + foreach (KeyValuePair cached in cache) + { + if (configuration.FirstOrDefault(x => x.Id == cached.Key) == null) + { + await mediator.PublishAsync(new Removed(cached.Value), + cancellationToken); + + cache.Remove(cached.Key); + } + } + + foreach (PrimaryCommandConfiguration item in configuration) + { + + //if (!cache.ContainsKey(item.Id)) + //{ + // factory.CreateAsync(item); + //} + //else + //{ + + //} + } } } diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs b/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs index 845b13d..37dbfdb 100644 --- a/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs +++ b/Hyperbar.Windows.Primary/PrimaryWidgetProvider.cs @@ -8,7 +8,7 @@ public class PrimaryWidgetProvider : { public void Create(HostBuilderContext comtext, IServiceCollection services) => services.AddConfiguration() - .AddTransient, ViewModelCache>() + .AddSingleton, ViewModelCache>() .AddTransient, WidgetComponentViewModelFactory>() .AddTransient, WidgetComponentViewModelEnumerator>() .AddWidgetTemplate() diff --git a/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs b/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs index 7fb0be6..21f9d08 100644 --- a/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs +++ b/Hyperbar.Windows.Primary/PrimaryWidgetViewModel.cs @@ -1,6 +1,4 @@ - - -namespace Hyperbar.Windows.Primary; +namespace Hyperbar.Windows.Primary; public class PrimaryWidgetViewModel(ITemplateFactory templateFactory, IServiceFactory serviceFactory, diff --git a/Hyperbar.Windows.Primary/WidgetComponentViewModelEnumerator.cs b/Hyperbar.Windows.Primary/WidgetComponentViewModelEnumerator.cs index bea8ffc..72d1a10 100644 --- a/Hyperbar.Windows.Primary/WidgetComponentViewModelEnumerator.cs +++ b/Hyperbar.Windows.Primary/WidgetComponentViewModelEnumerator.cs @@ -4,11 +4,11 @@ public class WidgetComponentViewModelEnumerator(PrimaryWidgetConfiguration confi IViewModelFactory factory) : IViewModelEnumerator { - public async IAsyncEnumerable Next() + public IEnumerable Next() { foreach (PrimaryCommandConfiguration item in configuration) { - yield return await factory.CreateAsync(item); + yield return factory.Create(item); } } } diff --git a/Hyperbar.Windows.Primary/WidgetComponentViewModelFactory.cs b/Hyperbar.Windows.Primary/WidgetComponentViewModelFactory.cs index d24ccf9..4dda507 100644 --- a/Hyperbar.Windows.Primary/WidgetComponentViewModelFactory.cs +++ b/Hyperbar.Windows.Primary/WidgetComponentViewModelFactory.cs @@ -7,9 +7,9 @@ public class WidgetComponentViewModelFactory(IServiceFactory service, IViewModelCache cache) : IViewModelFactory { - public async ValueTask CreateAsync(PrimaryCommandConfiguration value) + public IWidgetComponentViewModel? Create(PrimaryCommandConfiguration value) { - IWidgetComponentViewModel? viewModel = null; + IWidgetComponentViewModel? viewModel = default; if (value is KeyAcceleratorCommandConfiguration keyAcceleratorCommand) { @@ -31,6 +31,6 @@ public class WidgetComponentViewModelFactory(IServiceFactory service, cache.Add(value.Id, viewModel); } - return viewModel ?? default; + return viewModel; } } \ No newline at end of file diff --git a/Hyperbar/Hyperbar.csproj b/Hyperbar/Hyperbar.csproj index acc8244..29c2be8 100644 --- a/Hyperbar/Hyperbar.csproj +++ b/Hyperbar/Hyperbar.csproj @@ -7,6 +7,6 @@ - + diff --git a/Hyperbar/Lifecycles/IViewModelCache.cs b/Hyperbar/Lifecycles/IViewModelCache.cs new file mode 100644 index 0000000..fcb3e8a --- /dev/null +++ b/Hyperbar/Lifecycles/IViewModelCache.cs @@ -0,0 +1,8 @@ +namespace Hyperbar; + +public interface IViewModelCache : + IDictionary + where TKey : notnull +{ + +} diff --git a/Hyperbar/Lifecycles/IViewModelFactory.cs b/Hyperbar/Lifecycles/IViewModelFactory.cs index 5fd2a78..6d2c5ca 100644 --- a/Hyperbar/Lifecycles/IViewModelFactory.cs +++ b/Hyperbar/Lifecycles/IViewModelFactory.cs @@ -2,29 +2,5 @@ public interface IViewModelFactory { - ValueTask CreateAsync(TParameter value); + TViewModel? Create(TParameter value); } - -public interface IViewModelCache -{ - void Add(TKey key, TViewModel value); - - void Remove(TKey key); -} - -public class ViewModelCache : - IViewModelCache -{ - private readonly Dictionary cache = []; - - public void Add(TKey key, - TViewModel value) - { - cache.TryAdd(key, value); - } - - public void Remove(TKey key) - { - cache.Remove(key); - } -} \ No newline at end of file diff --git a/Hyperbar/Lifecycles/ViewModelCache.cs b/Hyperbar/Lifecycles/ViewModelCache.cs new file mode 100644 index 0000000..265ced6 --- /dev/null +++ b/Hyperbar/Lifecycles/ViewModelCache.cs @@ -0,0 +1,83 @@ +using System.Collections; +using System.Diagnostics.CodeAnalysis; +using System.Reactive.Disposables; + +namespace Hyperbar; + +public class ViewModelCache(IDisposer disposer) : + IViewModelCache + where TKey : notnull +{ + private readonly IDictionary cache = new Dictionary(); + + public TViewModel this[TKey key] + { + get => cache[key]; + set => cache[key] = value; + } + + public ICollection Keys => cache.Keys; + + public ICollection Values => cache.Values; + + public int Count => cache.Count; + + public bool IsReadOnly => false; + + public void Add(TKey key, TViewModel value) + { + disposer.Add(value!, Disposable.Create(() => + { + Remove(key); + })); + + cache.Add(key, value); + } + + public void Add(KeyValuePair item) + { + cache.Add(item); + } + + public void Clear() => cache.Clear(); + + public bool Contains(KeyValuePair item) + { + return cache.Contains(item); + } + + public bool ContainsKey(TKey key) + { + return cache.ContainsKey(key); + } + + public void CopyTo(KeyValuePair[] array, int arrayIndex) + { + cache.CopyTo(array, arrayIndex); + } + + public IEnumerator> GetEnumerator() + { + return cache.GetEnumerator(); + } + + public bool Remove(TKey key) + { + return cache.Remove(key); + } + + public bool Remove(KeyValuePair item) + { + return cache.Remove(item); + } + + public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TViewModel value) + { + return cache.TryGetValue(key, out value); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return cache.GetEnumerator(); + } +} \ No newline at end of file diff --git a/Hyperbar/Mediators/Mediator.cs b/Hyperbar/Mediators/Mediator.cs index c095f72..f0c7eeb 100644 --- a/Hyperbar/Mediators/Mediator.cs +++ b/Hyperbar/Mediators/Mediator.cs @@ -8,7 +8,7 @@ public class Mediator(IServiceProvider provider) : { private readonly ConditionalWeakTable subjects = []; - public ValueTask PublishAsync(TNotification notification, + public async ValueTask PublishAsync(TNotification notification, CancellationToken cancellationToken = default) where TNotification : INotification @@ -26,10 +26,8 @@ public class Mediator(IServiceProvider provider) : foreach (INotificationHandler handler in handlers) { - return handler.Handle(notification, cancellationToken); + await handler.Handle(notification, cancellationToken); } - - return default; } public ValueTask SendAsync(IRequest request, diff --git a/Hyperbar/Views/IViewModelEnumerator.cs b/Hyperbar/Views/IViewModelEnumerator.cs index b2b4188..cc0decf 100644 --- a/Hyperbar/Views/IViewModelEnumerator.cs +++ b/Hyperbar/Views/IViewModelEnumerator.cs @@ -2,5 +2,5 @@ public interface IViewModelEnumerator { - IAsyncEnumerable Next(); + IEnumerable Next(); } diff --git a/Hyperbar/Views/ObservableCollectionViewModel.cs b/Hyperbar/Views/ObservableCollectionViewModel.cs index 2b23f5d..4b0e10f 100644 --- a/Hyperbar/Views/ObservableCollectionViewModel.cs +++ b/Hyperbar/Views/ObservableCollectionViewModel.cs @@ -14,10 +14,9 @@ public partial class ObservableCollectionViewModel : { public ObservableCollection collection = []; private readonly SynchronizationContext? context; + private readonly IDisposer disposer; private readonly IViewModelEnumerator? enumerator; private readonly IServiceFactory serviceFactory; - private readonly IDisposer disposer; - public ObservableCollectionViewModel(IServiceFactory serviceFactory, IMediator mediator, IDisposer disposer) @@ -49,16 +48,13 @@ public partial class ObservableCollectionViewModel : if (enumerator is not null) { - Task.Run(async () => + foreach (TItem? item in enumerator.Next()) { - await foreach (TItem? item in enumerator.Next()) + if (item is not null) { - if (item is not null) - { - Add(item); - } + Add(item); } - }); + } } } @@ -150,15 +146,27 @@ public partial class ObservableCollectionViewModel : TItem { T? item = serviceFactory.Create(); - context?.Post(state => Add(item), null); + Add(item); return item; } public void Add(TItem item) { - int index = collection.Count; - InsertItem(index, item); + context?.Post(state => + { + disposer.Add(this, item); + disposer.Add(item, Disposable.Create(item, args => + { + if (Contains(args)) + { + Remove(args); + } + })); + + int index = collection.Count; + InsertItem(index, item); + }, null); } int IList.Add(object? value) @@ -182,27 +190,35 @@ public partial class ObservableCollectionViewModel : { foreach (TItem? item in items) { - context?.Post(state => Add(item), null); + Add(item); } } public void Clear() => ClearItems(); - public bool Contains(TItem item) => collection.Contains(item); + public bool Contains(TItem item) => + collection.Contains(item); - bool IList.Contains(object? value) => IsCompatibleObject(value) && Contains((TItem)value!); + bool IList.Contains(object? value) => + IsCompatibleObject(value) && Contains((TItem)value!); - public void CopyTo(TItem[] array, int index) => collection.CopyTo(array, index); + public void CopyTo(TItem[] array, int index) => + collection.CopyTo(array, index); - void ICollection.CopyTo(Array array, int index) => collection.CopyTo((TItem[])array, index); + void ICollection.CopyTo(Array array, int index) => + collection.CopyTo((TItem[])array, index); - public IEnumerator GetEnumerator() => collection.GetEnumerator(); + public IEnumerator GetEnumerator() => + collection.GetEnumerator(); - IEnumerator IEnumerable.GetEnumerator() => ((IEnumerable)collection).GetEnumerator(); + IEnumerator IEnumerable.GetEnumerator() => + ((IEnumerable)collection).GetEnumerator(); public int IndexOf(TItem item) => collection.IndexOf(item); - int IList.IndexOf(object? value) => IsCompatibleObject(value) ? IndexOf((TItem)value!) : -1; + int IList.IndexOf(object? value) => + IsCompatibleObject(value) ? + IndexOf((TItem)value!) : -1; public Task InitializeAsync() { @@ -215,9 +231,11 @@ public partial class ObservableCollectionViewModel : return Task.CompletedTask; } - public void Insert(int index, TItem item) => InsertItem(index, item); + public void Insert(int index, TItem item) => + InsertItem(index, item); - void IList.Insert(int index, object? value) + void IList.Insert(int index, + object? value) { if (value is TItem item) { @@ -229,7 +247,13 @@ public partial class ObservableCollectionViewModel : { int index = collection.IndexOf(item); if (index < 0) return false; - RemoveItem(index); + + context?.Post(state => + { + context?.Post(state => RemoveItem(index), null); + + }, null); + return true; } @@ -241,32 +265,23 @@ public partial class ObservableCollectionViewModel : } } - public void RemoveAt(int index) => RemoveItem(index); + public void RemoveAt(int index) => + RemoveItem(index); - protected virtual void ClearItems() => collection.Clear(); + protected virtual void ClearItems() => + collection.Clear(); - protected virtual void InsertItem(int index, TItem value) - { - if (value is TItem item) - { - disposer.Add(item, Disposable.Create(() => - { - if (Contains(item)) - { - Remove(item); - } - })); + protected virtual void InsertItem(int index, + TItem value) => collection.Insert(index, value); - context?.Post(state => collection.Insert(index, item), null); - } - } + protected virtual void RemoveItem(int index) => + collection.RemoveAt(index); - protected virtual void RemoveItem(int index) => collection.RemoveAt(index); + protected virtual void SetItem(int index, TItem item) => + collection[index] = item; - 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 static bool IsCompatibleObject(object? value) => + (value is TItem) || (value == null && default(TItem) == null); private void OnCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) => CollectionChanged?.Invoke(this, args); diff --git a/Hyperbar/Views/ObservableViewModel.cs b/Hyperbar/Views/ObservableViewModel.cs new file mode 100644 index 0000000..56a747a --- /dev/null +++ b/Hyperbar/Views/ObservableViewModel.cs @@ -0,0 +1,12 @@ +using CommunityToolkit.Mvvm.ComponentModel; + +namespace Hyperbar; + +public class ObservableViewModel(IServiceFactory serviceFactory, + IMediator mediator, + IDisposer disposer) : + ObservableObject, + IDisposable +{ + public void Dispose() => disposer.Dispose(this); +} diff --git a/Hyperbar/Views/WidgetButtonViewModel.cs b/Hyperbar/Views/WidgetButtonViewModel.cs index d506541..d8fa8b2 100644 --- a/Hyperbar/Views/WidgetButtonViewModel.cs +++ b/Hyperbar/Views/WidgetButtonViewModel.cs @@ -7,13 +7,10 @@ public partial class WidgetButtonViewModel(IServiceFactory serviceFactory, IMediator mediator, IDisposer disposer, ITemplateFactory templateFactory, - Guid id = default, + Guid guid = default, string? icon = null, - RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory) + RelayCommand? command = null) : WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory, guid) { - [ObservableProperty] - private Guid id = id; - [ObservableProperty] private string? icon = icon; diff --git a/Hyperbar/Views/WidgetComponentViewModel.cs b/Hyperbar/Views/WidgetComponentViewModel.cs index 47c3749..72a9e58 100644 --- a/Hyperbar/Views/WidgetComponentViewModel.cs +++ b/Hyperbar/Views/WidgetComponentViewModel.cs @@ -2,21 +2,43 @@ namespace Hyperbar; -public class ObservableViewModel(IServiceFactory serviceFactory, - IMediator mediator, - IDisposer disposer) : - ObservableObject, - IDisposable -{ - public void Dispose() => disposer.Dispose(this); -} - -public partial class WidgetComponentViewModel(IServiceFactory serviceFactory, - IMediator mediator, - IDisposer disposer, - ITemplateFactory templateFactory) : ObservableViewModel(serviceFactory, mediator, disposer), +public partial class WidgetComponentViewModel : + ObservableViewModel, IWidgetComponentViewModel, - ITemplatedViewModel + ITemplatedViewModel, + INotificationHandler> { - public ITemplateFactory TemplateFactory => templateFactory; + private readonly IMediator mediator; + private readonly IServiceFactory serviceFactory; + + [ObservableProperty] + private Guid id; + + public WidgetComponentViewModel(IServiceFactory serviceFactory, + IMediator mediator, + IDisposer disposer, + ITemplateFactory templateFactory, + Guid id = default) : base(serviceFactory, mediator, disposer) + { + this.serviceFactory = serviceFactory; + this.mediator = mediator; + this.id = id; + + TemplateFactory = templateFactory; + + mediator.Subscribe(this); + } + + public ITemplateFactory TemplateFactory { get; private set; } + + public ValueTask Handle(Removed notification, + CancellationToken cancellationToken) + { + if (notification.Value.Equals(this)) + { + Dispose(); + } + + return ValueTask.CompletedTask; + } } \ No newline at end of file