From f0ef3d1604e2d0416cdd4f9a27987efa4c90326f Mon Sep 17 00:00:00 2001 From: TheXamlGuy Date: Mon, 15 Jan 2024 22:02:30 +0000 Subject: [PATCH] Damn garbage collector --- .../Hyperbar.Windows.MediaController.csproj | 8 +- .../Backward.cs} | 2 +- .../{Play.cs => Lifecycles/Foward.cs} | 2 +- .../Lifecycles/MediaController.cs | 92 ++++++++++++++++++ .../MediaControllerFactory.cs | 0 .../Lifecycles/MediaControllerHandler.cs | 34 +++++++ .../Lifecycles/MediaControllerManager.cs | 70 ++++++++++++++ .../MediaControllerViewModelFactory.cs | 16 ++++ .../MediaControllerWidgetProvider.cs | 8 +- .../Lifecycles/MediaInformation.cs | 5 + .../{ => Lifecycles}/Pause.cs | 0 .../{Media.cs => Lifecycles/Play.cs} | 4 +- .../Playback.cs} | 2 +- .../MediaController.cs | 34 ------- .../MediaControllerHandler.cs | 28 ------ .../MediaControllerManager.cs | 58 ------------ .../MediaControllerViewModelFactory.cs | 16 ---- .../MediaInformationViewModel.cs | 20 ---- .../Views/MediaButtonView.xaml | 33 +++++++ .../Views/MediaButtonView.xaml.cs | 9 ++ .../Views/MediaButtonViewModel.cs | 20 ++++ .../{ => Views}/MediaControllerView.xaml | 2 +- .../{ => Views}/MediaControllerView.xaml.cs | 0 .../{ => Views}/MediaControllerViewModel.cs | 8 +- .../MediaControllerWidgetView.xaml | 0 .../MediaControllerWidgetView.xaml.cs | 0 .../MediaControllerWidgetViewModel.cs | 0 .../{ => Views}/MediaInformationView.xaml | 9 +- .../{ => Views}/MediaInformationView.xaml.cs | 0 .../Views/MediaInformationViewModel.cs | 38 ++++++++ Hyperbar.Windows/Views/WidgetView.xaml | 7 -- .../IServiceCollectionExtensions.cs | 2 +- Hyperbar/Lifecycles/Cache.cs | 8 +- Hyperbar/Lifecycles/Disposer.cs | 37 ++++++-- .../KeyAccelerator.cs | 0 Hyperbar/Lifecycles/Request.cs | 3 + .../{Commands => Lifecycles}/StartProcess.cs | 0 .../{Commands => Lifecycles}/VirtualKey.cs | 0 Hyperbar/Mediators/IDispatcher.cs | 6 ++ Hyperbar/Mediators/IMediator.cs | 5 + Hyperbar/Mediators/Mediator.cs | 11 +-- .../Views/IObservableCollectionViewModel.cs | 3 +- Hyperbar/Views/IViewModelInitialization.cs | 10 ++ Hyperbar/Views/IWidgetComponentViewModel.cs | 2 +- Hyperbar/Views/IWidgetViewModel.cs | 2 +- .../Views/ObservableCollectionViewModel.cs | 93 +++++++++---------- Hyperbar/Views/WidgetComponentViewModel.cs | 2 + 47 files changed, 452 insertions(+), 257 deletions(-) rename Hyperbar.Windows.MediaController/{FowardRequest.cs => Lifecycles/Backward.cs} (51%) rename Hyperbar.Windows.MediaController/{Play.cs => Lifecycles/Foward.cs} (55%) create mode 100644 Hyperbar.Windows.MediaController/Lifecycles/MediaController.cs rename Hyperbar.Windows.MediaController/{ => Lifecycles}/MediaControllerFactory.cs (100%) create mode 100644 Hyperbar.Windows.MediaController/Lifecycles/MediaControllerHandler.cs create mode 100644 Hyperbar.Windows.MediaController/Lifecycles/MediaControllerManager.cs create mode 100644 Hyperbar.Windows.MediaController/Lifecycles/MediaControllerViewModelFactory.cs rename Hyperbar.Windows.MediaController/{ => Lifecycles}/MediaControllerWidgetProvider.cs (78%) create mode 100644 Hyperbar.Windows.MediaController/Lifecycles/MediaInformation.cs rename Hyperbar.Windows.MediaController/{ => Lifecycles}/Pause.cs (100%) rename Hyperbar.Windows.MediaController/{Media.cs => Lifecycles/Play.cs} (57%) rename Hyperbar.Windows.MediaController/{BackwardRequest.cs => Lifecycles/Playback.cs} (50%) delete mode 100644 Hyperbar.Windows.MediaController/MediaController.cs delete mode 100644 Hyperbar.Windows.MediaController/MediaControllerHandler.cs delete mode 100644 Hyperbar.Windows.MediaController/MediaControllerManager.cs delete mode 100644 Hyperbar.Windows.MediaController/MediaControllerViewModelFactory.cs delete mode 100644 Hyperbar.Windows.MediaController/MediaInformationViewModel.cs create mode 100644 Hyperbar.Windows.MediaController/Views/MediaButtonView.xaml create mode 100644 Hyperbar.Windows.MediaController/Views/MediaButtonView.xaml.cs create mode 100644 Hyperbar.Windows.MediaController/Views/MediaButtonViewModel.cs rename Hyperbar.Windows.MediaController/{ => Views}/MediaControllerView.xaml (94%) rename Hyperbar.Windows.MediaController/{ => Views}/MediaControllerView.xaml.cs (100%) rename Hyperbar.Windows.MediaController/{ => Views}/MediaControllerViewModel.cs (61%) rename Hyperbar.Windows.MediaController/{ => Views}/MediaControllerWidgetView.xaml (100%) rename Hyperbar.Windows.MediaController/{ => Views}/MediaControllerWidgetView.xaml.cs (100%) rename Hyperbar.Windows.MediaController/{ => Views}/MediaControllerWidgetViewModel.cs (100%) rename Hyperbar.Windows.MediaController/{ => Views}/MediaInformationView.xaml (63%) rename Hyperbar.Windows.MediaController/{ => Views}/MediaInformationView.xaml.cs (100%) create mode 100644 Hyperbar.Windows.MediaController/Views/MediaInformationViewModel.cs rename Hyperbar/{Commands => Lifecycles}/KeyAccelerator.cs (100%) create mode 100644 Hyperbar/Lifecycles/Request.cs rename Hyperbar/{Commands => Lifecycles}/StartProcess.cs (100%) rename Hyperbar/{Commands => Lifecycles}/VirtualKey.cs (100%) create mode 100644 Hyperbar/Mediators/IDispatcher.cs create mode 100644 Hyperbar/Views/IViewModelInitialization.cs diff --git a/Hyperbar.Windows.MediaController/Hyperbar.Windows.MediaController.csproj b/Hyperbar.Windows.MediaController/Hyperbar.Windows.MediaController.csproj index e9aa82a..229b8f3 100644 --- a/Hyperbar.Windows.MediaController/Hyperbar.Windows.MediaController.csproj +++ b/Hyperbar.Windows.MediaController/Hyperbar.Windows.MediaController.csproj @@ -8,19 +8,13 @@ enable enable - - - - - - - + diff --git a/Hyperbar.Windows.MediaController/FowardRequest.cs b/Hyperbar.Windows.MediaController/Lifecycles/Backward.cs similarity index 51% rename from Hyperbar.Windows.MediaController/FowardRequest.cs rename to Hyperbar.Windows.MediaController/Lifecycles/Backward.cs index 3cfae01..d7bcf7c 100644 --- a/Hyperbar.Windows.MediaController/FowardRequest.cs +++ b/Hyperbar.Windows.MediaController/Lifecycles/Backward.cs @@ -1,3 +1,3 @@ namespace Hyperbar.Windows.MediaController; -public record FowardRequest : INotification; +public record Backward : INotification; diff --git a/Hyperbar.Windows.MediaController/Play.cs b/Hyperbar.Windows.MediaController/Lifecycles/Foward.cs similarity index 55% rename from Hyperbar.Windows.MediaController/Play.cs rename to Hyperbar.Windows.MediaController/Lifecycles/Foward.cs index 41ee48a..4db2cbc 100644 --- a/Hyperbar.Windows.MediaController/Play.cs +++ b/Hyperbar.Windows.MediaController/Lifecycles/Foward.cs @@ -1,3 +1,3 @@ namespace Hyperbar.Windows.MediaController; -public record Play : INotification; +public record Foward : INotification; diff --git a/Hyperbar.Windows.MediaController/Lifecycles/MediaController.cs b/Hyperbar.Windows.MediaController/Lifecycles/MediaController.cs new file mode 100644 index 0000000..0c73287 --- /dev/null +++ b/Hyperbar.Windows.MediaController/Lifecycles/MediaController.cs @@ -0,0 +1,92 @@ +using Windows.Media.Control; + +namespace Hyperbar.Windows.MediaController; + +public class MediaController : + INotificationHandler, + INotificationHandler, + INotificationHandler>, + INotificationHandler>, + IDisposable +{ + private readonly IMediator mediator; + private readonly IDisposer disposer; + private readonly GlobalSystemMediaTransportControlsSession session; + private readonly AsyncLock asyncLock = new(); + + public MediaController(IMediator mediator, + IDisposer disposer, + GlobalSystemMediaTransportControlsSession session) + { + this.mediator = mediator; + this.disposer = disposer; + this.session = session; + + disposer.Add(this); + mediator.Subscribe(this); + + session.MediaPropertiesChanged += OnMediaPropertiesChanged; + session.PlaybackInfoChanged += OnPlaybackInfoChanged; + } + + public void Dispose() + { + disposer.Dispose(this); + } + + public async ValueTask Handle(Play notification, + CancellationToken cancellationToken) => + await session.TryPlayAsync(); + + public async ValueTask Handle(Pause notification, + CancellationToken cancellationToken) => + await session.TryPauseAsync(); + + public async ValueTask Handle(Request notification, + CancellationToken cancellationToken) + { + await mediator.PublishAsync(new Changed(), cancellationToken); + } + + public async ValueTask Handle(Request _, + CancellationToken cancellationToken) + { + using (await asyncLock) + { + try + { + GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties = await session.TryGetMediaPropertiesAsync(); + await mediator.PublishAsync(new Changed(new MediaInformation(mediaProperties.Title, + mediaProperties.Subtitle)), cancellationToken); + } + catch + { + + } + } + } + + private async void OnMediaPropertiesChanged(GlobalSystemMediaTransportControlsSession sender, + MediaPropertiesChangedEventArgs args) + { + using (await asyncLock) + { + try + { + GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties = await session.TryGetMediaPropertiesAsync(); + await mediator.PublishAsync(new Changed(new MediaInformation(mediaProperties.Title, + mediaProperties.Artist))); + } + catch + { + + } + } + } + + private async void OnPlaybackInfoChanged(GlobalSystemMediaTransportControlsSession sender, + PlaybackInfoChangedEventArgs args) + { + await mediator.PublishAsync(new Changed()); + } +} \ No newline at end of file diff --git a/Hyperbar.Windows.MediaController/MediaControllerFactory.cs b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerFactory.cs similarity index 100% rename from Hyperbar.Windows.MediaController/MediaControllerFactory.cs rename to Hyperbar.Windows.MediaController/Lifecycles/MediaControllerFactory.cs diff --git a/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerHandler.cs b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerHandler.cs new file mode 100644 index 0000000..0fcf96e --- /dev/null +++ b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerHandler.cs @@ -0,0 +1,34 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Hyperbar.Windows.MediaController; + +public class MediaControllerHandler(IMediator mediator, + IServiceScopeProvider scopeProvider, + ICache cache) : + INotificationHandler> +{ + public async ValueTask Handle(Created notification, + CancellationToken cancellationToken) + { + if (notification.Value is MediaController mediaController) + { + if (scopeProvider.TryGet(mediaController, out IServiceScope? serviceScope)) + { + if (serviceScope is not null) + { + if (serviceScope.ServiceProvider.GetService>() + is IFactory factory) + { + if (factory.Create(mediaController) is MediaControllerViewModel mediaControllerViewModel) + { + cache.Add(mediaController, mediaControllerViewModel); + await mediator.PublishAsync(new Created(mediaControllerViewModel), + cancellationToken); + } + } + } + } + } + + } +} diff --git a/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerManager.cs b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerManager.cs new file mode 100644 index 0000000..e7b6a45 --- /dev/null +++ b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerManager.cs @@ -0,0 +1,70 @@ +using System.Diagnostics; +using Windows.Media.Control; + +namespace Hyperbar.Windows.MediaController; +public class MediaControllerManager(IMediator mediator, + IFactory factory, + IDispatcher dispatcher, + IDisposer disposer) : + IInitializer +{ + private readonly AsyncLock asyncLock = new(); + private readonly List> cache = []; + private GlobalSystemMediaTransportControlsSessionManager? mediaTransportControlsSessionManager; + + public async Task InitializeAsync() + { + mediaTransportControlsSessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); + mediaTransportControlsSessionManager.SessionsChanged += OnSessionsChanged; + + IReadOnlyList sessions = + mediaTransportControlsSessionManager.GetSessions(); + + foreach (GlobalSystemMediaTransportControlsSession session in sessions) + { + await InitializeSessionAsync(session); + } + } + + private async Task InitializeSessionAsync(GlobalSystemMediaTransportControlsSession session) + { + if (factory.Create(session) is MediaController mediaController) + { + await mediator.PublishAsync(new Created(mediaController)); + Debug.WriteLine("Added " + session.SourceAppUserModelId + " " + session); + + cache.Add(new KeyValuePair(session, mediaController)); + } + } + + private async void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender, + SessionsChangedEventArgs args) + { + IReadOnlyList sessions = + sender.GetSessions(); + + using (await asyncLock) + { + foreach (KeyValuePair session in + cache.ToList()) + { + if (!sessions.Any(x => x.SourceAppUserModelId == session.Key.SourceAppUserModelId)) + { + await dispatcher.InvokeAsync(() => disposer.Dispose(session.Value)); + cache.Remove(session); + } + } + } + + using (await asyncLock) + { + foreach (GlobalSystemMediaTransportControlsSession session in sessions) + { + if (!cache.Any(x => x.Key.SourceAppUserModelId == session.SourceAppUserModelId)) + { + await InitializeSessionAsync(session); + } + } + } + } +} diff --git a/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerViewModelFactory.cs b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerViewModelFactory.cs new file mode 100644 index 0000000..7d6ab3c --- /dev/null +++ b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerViewModelFactory.cs @@ -0,0 +1,16 @@ +namespace Hyperbar.Windows.MediaController; + +public class MediaControllerViewModelFactory(IServiceFactory service) : + IFactory +{ + public MediaControllerViewModel? Create(MediaController value) + { + if (service.Create() + is MediaControllerViewModel widgetComponentViewModel) + { + return widgetComponentViewModel; + } + + return default; + } +} diff --git a/Hyperbar.Windows.MediaController/MediaControllerWidgetProvider.cs b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerWidgetProvider.cs similarity index 78% rename from Hyperbar.Windows.MediaController/MediaControllerWidgetProvider.cs rename to Hyperbar.Windows.MediaController/Lifecycles/MediaControllerWidgetProvider.cs index 0634958..d0055ba 100644 --- a/Hyperbar.Windows.MediaController/MediaControllerWidgetProvider.cs +++ b/Hyperbar.Windows.MediaController/Lifecycles/MediaControllerWidgetProvider.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; -using System.Collections.Concurrent; using Windows.Media.Control; namespace Hyperbar.Windows.MediaController; @@ -16,8 +15,9 @@ public class MediaControllerWidgetProvider : .AddCache() .AddTransient, MediaControllerFactory>() .AddHandler() - .AddTransient, MediaControllerViewModelFactory>() - .AddCache() + .AddTransient, MediaControllerViewModelFactory>() + .AddCache() .AddContentTemplate() - .AddContentTemplate(); + .AddContentTemplate() + .AddContentTemplate(); } \ No newline at end of file diff --git a/Hyperbar.Windows.MediaController/Lifecycles/MediaInformation.cs b/Hyperbar.Windows.MediaController/Lifecycles/MediaInformation.cs new file mode 100644 index 0000000..d033acf --- /dev/null +++ b/Hyperbar.Windows.MediaController/Lifecycles/MediaInformation.cs @@ -0,0 +1,5 @@ +namespace Hyperbar.Windows.MediaController; + +public record MediaInformation(string Title, string Description); + + diff --git a/Hyperbar.Windows.MediaController/Pause.cs b/Hyperbar.Windows.MediaController/Lifecycles/Pause.cs similarity index 100% rename from Hyperbar.Windows.MediaController/Pause.cs rename to Hyperbar.Windows.MediaController/Lifecycles/Pause.cs diff --git a/Hyperbar.Windows.MediaController/Media.cs b/Hyperbar.Windows.MediaController/Lifecycles/Play.cs similarity index 57% rename from Hyperbar.Windows.MediaController/Media.cs rename to Hyperbar.Windows.MediaController/Lifecycles/Play.cs index ebd483e..6b86f2d 100644 --- a/Hyperbar.Windows.MediaController/Media.cs +++ b/Hyperbar.Windows.MediaController/Lifecycles/Play.cs @@ -1,5 +1,3 @@ namespace Hyperbar.Windows.MediaController; -public record Media; - - +public record Play : INotification; \ No newline at end of file diff --git a/Hyperbar.Windows.MediaController/BackwardRequest.cs b/Hyperbar.Windows.MediaController/Lifecycles/Playback.cs similarity index 50% rename from Hyperbar.Windows.MediaController/BackwardRequest.cs rename to Hyperbar.Windows.MediaController/Lifecycles/Playback.cs index bad2f0a..3ed1095 100644 --- a/Hyperbar.Windows.MediaController/BackwardRequest.cs +++ b/Hyperbar.Windows.MediaController/Lifecycles/Playback.cs @@ -1,3 +1,3 @@ namespace Hyperbar.Windows.MediaController; -public record BackwardRequest : INotification; +public record Playback : INotification; diff --git a/Hyperbar.Windows.MediaController/MediaController.cs b/Hyperbar.Windows.MediaController/MediaController.cs deleted file mode 100644 index 9987288..0000000 --- a/Hyperbar.Windows.MediaController/MediaController.cs +++ /dev/null @@ -1,34 +0,0 @@ -using Windows.Media.Control; - -namespace Hyperbar.Windows.MediaController; - -public class MediaController : - INotificationHandler, - INotificationHandler -{ - private readonly IMediator mediator; - private readonly GlobalSystemMediaTransportControlsSession session; - - public MediaController(IMediator mediator, - GlobalSystemMediaTransportControlsSession session) - { - this.mediator = mediator; - this.session = session; - - mediator.Subscribe(this); - - session.MediaPropertiesChanged += OnMediaPropertiesChanged; - } - - private void OnMediaPropertiesChanged(GlobalSystemMediaTransportControlsSession sender, - MediaPropertiesChangedEventArgs args) - { - mediator.PublishAsync(new Changed()); - } - - public async ValueTask Handle(Play notification, CancellationToken cancellationToken) => - await session.TryPlayAsync(); - - public async ValueTask Handle(Pause notification, CancellationToken cancellationToken) => - await session.TryPauseAsync(); -} \ No newline at end of file diff --git a/Hyperbar.Windows.MediaController/MediaControllerHandler.cs b/Hyperbar.Windows.MediaController/MediaControllerHandler.cs deleted file mode 100644 index bfc9eab..0000000 --- a/Hyperbar.Windows.MediaController/MediaControllerHandler.cs +++ /dev/null @@ -1,28 +0,0 @@ -using Microsoft.Extensions.DependencyInjection; - -namespace Hyperbar.Windows.MediaController; - -public class MediaControllerHandler(IMediator mediator, - IServiceScopeProvider scopeProvider) : - INotificationHandler> -{ - public async ValueTask Handle(Created notification, - CancellationToken cancellationToken) - { - if (scopeProvider.TryGet(notification.Value, out IServiceScope? serviceScope)) - { - if (serviceScope is not null) - { - if (serviceScope.ServiceProvider.GetService>() - is IFactory factory) - { - if (factory.Create() is MediaControllerViewModel mediaControllerViewModel) - { - await mediator.PublishAsync(new Created(mediaControllerViewModel), - cancellationToken); - } - } - } - } - } -} diff --git a/Hyperbar.Windows.MediaController/MediaControllerManager.cs b/Hyperbar.Windows.MediaController/MediaControllerManager.cs deleted file mode 100644 index 70571b1..0000000 --- a/Hyperbar.Windows.MediaController/MediaControllerManager.cs +++ /dev/null @@ -1,58 +0,0 @@ -using System.Collections.Concurrent; -using Windows.Media.Control; - -namespace Hyperbar.Windows.MediaController; -public class MediaControllerManager(IMediator mediator, - IFactory factory) : - IInitializer -{ - private readonly List> cachedSessions = []; - - public async Task InitializeAsync() - { - GlobalSystemMediaTransportControlsSessionManager mediaTransportControlsSessionManager = - await GlobalSystemMediaTransportControlsSessionManager.RequestAsync(); - mediaTransportControlsSessionManager.SessionsChanged += OnSessionsChanged; - - IReadOnlyList sessions = - mediaTransportControlsSessionManager.GetSessions(); - - foreach (GlobalSystemMediaTransportControlsSession session in sessions) - { - await InitializeSessionAsync(session); - } - } - - private async Task InitializeSessionAsync(GlobalSystemMediaTransportControlsSession session) - { - if (factory.Create(session) is MediaController mediaController) - { - await mediator.PublishAsync(new Created(mediaController)); - cachedSessions.Add(new KeyValuePair(session, mediaController)); - } - } - - private async void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender, - SessionsChangedEventArgs args) - { - IReadOnlyList sessions = - sender.GetSessions(); - - foreach (KeyValuePair session in - cachedSessions.ToList()) - { - if (!sessions.Any(x => x.SourceAppUserModelId == session.Key.SourceAppUserModelId)) - { - cachedSessions.Remove(session); - } - } - - foreach (GlobalSystemMediaTransportControlsSession session in sessions) - { - if (!cachedSessions.Any(x => x.Key.SourceAppUserModelId == session.SourceAppUserModelId)) - { - await InitializeSessionAsync(session); - } - } - } -} diff --git a/Hyperbar.Windows.MediaController/MediaControllerViewModelFactory.cs b/Hyperbar.Windows.MediaController/MediaControllerViewModelFactory.cs deleted file mode 100644 index 4973c47..0000000 --- a/Hyperbar.Windows.MediaController/MediaControllerViewModelFactory.cs +++ /dev/null @@ -1,16 +0,0 @@ -namespace Hyperbar.Windows.MediaController; - -public class MediaControllerViewModelFactory(IServiceFactory service, ICache cache) : - IFactory -{ - public MediaControllerViewModel? Create() - { - if (service.Create() is MediaControllerViewModel widgetComponentViewModel) - { - cache.Add(widgetComponentViewModel); - return widgetComponentViewModel; - } - - return default; - } -} diff --git a/Hyperbar.Windows.MediaController/MediaInformationViewModel.cs b/Hyperbar.Windows.MediaController/MediaInformationViewModel.cs deleted file mode 100644 index b127c1c..0000000 --- a/Hyperbar.Windows.MediaController/MediaInformationViewModel.cs +++ /dev/null @@ -1,20 +0,0 @@ -using CommunityToolkit.Mvvm.ComponentModel; - -namespace Hyperbar.Windows.MediaController; - -public partial class MediaInformationViewModel : - WidgetComponentViewModel -{ - [ObservableProperty] - private string title = "this is a test"; - - [ObservableProperty] - private string description = "this is a test description"; - - public MediaInformationViewModel(IServiceFactory serviceFactory, - IMediator mediator, - IDisposer disposer, - ITemplateFactory templateFactory) : base(serviceFactory, mediator, disposer, templateFactory) - { - } -} diff --git a/Hyperbar.Windows.MediaController/Views/MediaButtonView.xaml b/Hyperbar.Windows.MediaController/Views/MediaButtonView.xaml new file mode 100644 index 0000000..25f0f53 --- /dev/null +++ b/Hyperbar.Windows.MediaController/Views/MediaButtonView.xaml @@ -0,0 +1,33 @@ + + + + + + + + + 0 + 40 + 40 + + + diff --git a/Hyperbar.Windows.MediaController/Views/MediaButtonView.xaml.cs b/Hyperbar.Windows.MediaController/Views/MediaButtonView.xaml.cs new file mode 100644 index 0000000..7785dfd --- /dev/null +++ b/Hyperbar.Windows.MediaController/Views/MediaButtonView.xaml.cs @@ -0,0 +1,9 @@ +using Microsoft.UI.Xaml.Controls; + +namespace Hyperbar.Windows.MediaController; + +public sealed partial class MediaButtonView : + UserControl +{ + public MediaButtonView() => InitializeComponent(); +} diff --git a/Hyperbar.Windows.MediaController/Views/MediaButtonViewModel.cs b/Hyperbar.Windows.MediaController/Views/MediaButtonViewModel.cs new file mode 100644 index 0000000..96c1f1f --- /dev/null +++ b/Hyperbar.Windows.MediaController/Views/MediaButtonViewModel.cs @@ -0,0 +1,20 @@ +using CommunityToolkit.Mvvm.Input; +using System.Windows.Input; + +namespace Hyperbar.Windows.MediaController; + +public class MediaButtonViewModel(IServiceFactory serviceFactory, + IMediator mediator, + IDisposer disposer, + ITemplateFactory templateFactory, + Guid guid = default, + string? text = null, + string? icon = null, + RelayCommand? command = null) : + WidgetButtonViewModel(serviceFactory, mediator, disposer, templateFactory, guid, text, icon, command), + IViewModelInitialization +{ + public ICommand Initialize => new AsyncRelayCommand(InitializeAsync); + + public async Task InitializeAsync() => await Mediator.PublishAsync>(); +} diff --git a/Hyperbar.Windows.MediaController/MediaControllerView.xaml b/Hyperbar.Windows.MediaController/Views/MediaControllerView.xaml similarity index 94% rename from Hyperbar.Windows.MediaController/MediaControllerView.xaml rename to Hyperbar.Windows.MediaController/Views/MediaControllerView.xaml index d62effb..3747274 100644 --- a/Hyperbar.Windows.MediaController/MediaControllerView.xaml +++ b/Hyperbar.Windows.MediaController/Views/MediaControllerView.xaml @@ -4,7 +4,7 @@ xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ui="using:Hyperbar.Windows.UI"> - + (); - Add("Backward", "\uEB9E"); - Add("Play", "\uE768", new RelayCommand(async () => await mediator.SendAsync(new Play()))); - Add("Pause", "\uE769", new RelayCommand(async () => await mediator.PublishAsync(new Pause()))); - Add("Forward", "\uEB9D"); + Add("Backward", "\uEB9E"); + Add("Play", "\uE768", new RelayCommand(async () => await mediator.PublishAsync())); + Add("Pause", "\uE769", new RelayCommand(async () => await mediator.PublishAsync())); + Add("Forward", "\uEB9D"); } public ITemplateFactory TemplateFactory { get; set; } diff --git a/Hyperbar.Windows.MediaController/MediaControllerWidgetView.xaml b/Hyperbar.Windows.MediaController/Views/MediaControllerWidgetView.xaml similarity index 100% rename from Hyperbar.Windows.MediaController/MediaControllerWidgetView.xaml rename to Hyperbar.Windows.MediaController/Views/MediaControllerWidgetView.xaml diff --git a/Hyperbar.Windows.MediaController/MediaControllerWidgetView.xaml.cs b/Hyperbar.Windows.MediaController/Views/MediaControllerWidgetView.xaml.cs similarity index 100% rename from Hyperbar.Windows.MediaController/MediaControllerWidgetView.xaml.cs rename to Hyperbar.Windows.MediaController/Views/MediaControllerWidgetView.xaml.cs diff --git a/Hyperbar.Windows.MediaController/MediaControllerWidgetViewModel.cs b/Hyperbar.Windows.MediaController/Views/MediaControllerWidgetViewModel.cs similarity index 100% rename from Hyperbar.Windows.MediaController/MediaControllerWidgetViewModel.cs rename to Hyperbar.Windows.MediaController/Views/MediaControllerWidgetViewModel.cs diff --git a/Hyperbar.Windows.MediaController/MediaInformationView.xaml b/Hyperbar.Windows.MediaController/Views/MediaInformationView.xaml similarity index 63% rename from Hyperbar.Windows.MediaController/MediaInformationView.xaml rename to Hyperbar.Windows.MediaController/Views/MediaInformationView.xaml index 597b3ce..f139106 100644 --- a/Hyperbar.Windows.MediaController/MediaInformationView.xaml +++ b/Hyperbar.Windows.MediaController/Views/MediaInformationView.xaml @@ -2,7 +2,9 @@ + xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" + xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" + xmlns:interactivity="using:Microsoft.Xaml.Interactivity"> @@ -20,6 +22,11 @@ Opacity="0.7" Style="{ThemeResource CaptionTextBlockStyle}" Text="{Binding Description}" /> + + + + + diff --git a/Hyperbar.Windows.MediaController/MediaInformationView.xaml.cs b/Hyperbar.Windows.MediaController/Views/MediaInformationView.xaml.cs similarity index 100% rename from Hyperbar.Windows.MediaController/MediaInformationView.xaml.cs rename to Hyperbar.Windows.MediaController/Views/MediaInformationView.xaml.cs diff --git a/Hyperbar.Windows.MediaController/Views/MediaInformationViewModel.cs b/Hyperbar.Windows.MediaController/Views/MediaInformationViewModel.cs new file mode 100644 index 0000000..3f6035a --- /dev/null +++ b/Hyperbar.Windows.MediaController/Views/MediaInformationViewModel.cs @@ -0,0 +1,38 @@ +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; +using System.Windows.Input; + +namespace Hyperbar.Windows.MediaController; + +public partial class MediaInformationViewModel(IServiceFactory serviceFactory, + IMediator mediator, + IDisposer disposer, + ITemplateFactory templateFactory) : + WidgetComponentViewModel(serviceFactory, mediator, disposer, templateFactory), + IViewModelInitialization, + INotificationHandler> +{ + [ObservableProperty] + private string? description; + + [ObservableProperty] + private string? title; + + public ICommand Initialize => + new AsyncRelayCommand(InitializeAsync); + + public ValueTask Handle(Changed notification, + CancellationToken cancellationToken) + { + if (notification.Value is MediaInformation value) + { + Title = value.Title; + Description = value.Description; + } + + return ValueTask.CompletedTask; + } + + public async Task InitializeAsync() => + await Mediator.PublishAsync>(); +} diff --git a/Hyperbar.Windows/Views/WidgetView.xaml b/Hyperbar.Windows/Views/WidgetView.xaml index 95b55fa..a1d793c 100644 --- a/Hyperbar.Windows/Views/WidgetView.xaml +++ b/Hyperbar.Windows/Views/WidgetView.xaml @@ -3,8 +3,6 @@ x:Class="Hyperbar.Windows.WidgetView" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" - xmlns:interactions="using:Microsoft.Xaml.Interactions.Core" - xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:ui="using:Hyperbar.Windows.UI"> @@ -12,10 +10,5 @@ - - - - - \ No newline at end of file diff --git a/Hyperbar/Extensions/IServiceCollectionExtensions.cs b/Hyperbar/Extensions/IServiceCollectionExtensions.cs index e5f41f3..e57af4d 100644 --- a/Hyperbar/Extensions/IServiceCollectionExtensions.cs +++ b/Hyperbar/Extensions/IServiceCollectionExtensions.cs @@ -13,7 +13,7 @@ public static class IServiceCollectionExtensions where TKey : notnull { - services.AddSingleton, Cache>(); + services.AddScoped, Cache>(); services.AddTransient(provider => provider.GetService>()!.Select(x => x.Value)); return services; diff --git a/Hyperbar/Lifecycles/Cache.cs b/Hyperbar/Lifecycles/Cache.cs index c9015fb..e75b3a9 100644 --- a/Hyperbar/Lifecycles/Cache.cs +++ b/Hyperbar/Lifecycles/Cache.cs @@ -31,18 +31,20 @@ public class Cache(IDisposer disposer) : public class Cache(IDisposer disposer) : ICache where TKey : notnull + where TValue : notnull { private readonly ConcurrentDictionary cache = new(); public void Add(TKey key, TValue value) { - disposer.Add(value!, Disposable.Create(() => + cache.TryAdd(key, value); + + disposer.Add(key, Disposable.Create(() => { + disposer.Dispose(value); Remove(key); })); - - cache.TryAdd(key, value); } public void Clear() => cache.Clear(); diff --git a/Hyperbar/Lifecycles/Disposer.cs b/Hyperbar/Lifecycles/Disposer.cs index 6759ba0..d223980 100644 --- a/Hyperbar/Lifecycles/Disposer.cs +++ b/Hyperbar/Lifecycles/Disposer.cs @@ -1,18 +1,39 @@ using System.Runtime.CompilerServices; using System.Reactive.Disposables; using System.Collections; +using System.Collections.Concurrent; namespace Hyperbar; +public class AsyncLock(int initial = 1, + int maximum = 1) : IDisposable +{ + private readonly SemaphoreSlim semaphore = new(initial, maximum); + + public void Dispose() + { + semaphore.Release(); + } + + public TaskAwaiter GetAwaiter() => LockAsync().GetAwaiter(); + + private async Task LockAsync() + { + await semaphore.WaitAsync(); + return this; + } +} + + public class Disposer : IDisposer { - private readonly ConditionalWeakTable subjects = []; + private readonly ConcurrentDictionary subjects = []; public void Add(object subject, params object[] objects) { - CompositeDisposable disposables = subjects.GetOrCreateValue(subject); + CompositeDisposable disposables = subjects.GetOrAdd(subject, args => new CompositeDisposable()); foreach (IDisposable disposable in objects.OfType()) { disposables.Add(disposable); @@ -26,17 +47,17 @@ public class Disposer : private void FromNotDisposable(object target) { - if (target is IEnumerable enumerableTarget) + if (target is IList collection && collection is { Count: > 0 }) { - foreach (object? item in enumerableTarget) + foreach (object? item in collection) { FromNotDisposable(item); } } - if (target is IDisposable disposableTarget) + if (target is IDisposable disposable) { - disposableTarget.Dispose(); + disposable.Dispose(); } if (target is not IDisposable) @@ -51,7 +72,7 @@ public class Disposer : where TDisposable : IDisposable { - CompositeDisposable disposables = subjects.GetOrCreateValue(subject); + CompositeDisposable disposables = subjects.GetOrAdd(subject, args => new CompositeDisposable()); if (disposer is not null) { disposables.Remove(disposer); @@ -64,7 +85,7 @@ public class Disposer : public void Remove(object subject, IDisposable disposer) { - CompositeDisposable disposables = subjects.GetOrCreateValue(subject); + CompositeDisposable disposables = subjects.GetOrAdd(subject, args => new CompositeDisposable()); if (disposer is not null) { disposables.Remove(disposer); diff --git a/Hyperbar/Commands/KeyAccelerator.cs b/Hyperbar/Lifecycles/KeyAccelerator.cs similarity index 100% rename from Hyperbar/Commands/KeyAccelerator.cs rename to Hyperbar/Lifecycles/KeyAccelerator.cs diff --git a/Hyperbar/Lifecycles/Request.cs b/Hyperbar/Lifecycles/Request.cs new file mode 100644 index 0000000..9c4ee62 --- /dev/null +++ b/Hyperbar/Lifecycles/Request.cs @@ -0,0 +1,3 @@ +namespace Hyperbar; + +public record Request : INotification; diff --git a/Hyperbar/Commands/StartProcess.cs b/Hyperbar/Lifecycles/StartProcess.cs similarity index 100% rename from Hyperbar/Commands/StartProcess.cs rename to Hyperbar/Lifecycles/StartProcess.cs diff --git a/Hyperbar/Commands/VirtualKey.cs b/Hyperbar/Lifecycles/VirtualKey.cs similarity index 100% rename from Hyperbar/Commands/VirtualKey.cs rename to Hyperbar/Lifecycles/VirtualKey.cs diff --git a/Hyperbar/Mediators/IDispatcher.cs b/Hyperbar/Mediators/IDispatcher.cs new file mode 100644 index 0000000..b5a0daa --- /dev/null +++ b/Hyperbar/Mediators/IDispatcher.cs @@ -0,0 +1,6 @@ +namespace Hyperbar; + +public interface IDispatcher +{ + Task InvokeAsync(Action action); +} diff --git a/Hyperbar/Mediators/IMediator.cs b/Hyperbar/Mediators/IMediator.cs index 4ec6fd0..96b10c5 100644 --- a/Hyperbar/Mediators/IMediator.cs +++ b/Hyperbar/Mediators/IMediator.cs @@ -7,6 +7,11 @@ public interface IMediator where TNotification : INotification; + ValueTask PublishAsync(CancellationToken cancellationToken = default) + where TNotification : + INotification, + new(); + ValueTask SendAsync(IRequest request, CancellationToken cancellationToken = default); diff --git a/Hyperbar/Mediators/Mediator.cs b/Hyperbar/Mediators/Mediator.cs index 8beb121..01030d9 100644 --- a/Hyperbar/Mediators/Mediator.cs +++ b/Hyperbar/Mediators/Mediator.cs @@ -1,14 +1,8 @@ using Microsoft.Extensions.DependencyInjection; using System.Collections.Concurrent; -using System.Reactive.Concurrency; namespace Hyperbar; -public interface IDispatcher -{ - Task InvokeAsync(Action action); -} - public class Mediator(IServiceProvider provider, IDispatcher dispatcher) : IMediator @@ -42,6 +36,11 @@ public class Mediator(IServiceProvider provider, } } + public ValueTask PublishAsync(CancellationToken cancellationToken = default) + where TNotification : + INotification, + new() => PublishAsync(new TNotification(), cancellationToken); + public ValueTask SendAsync(IRequest request, CancellationToken cancellationToken = default) { diff --git a/Hyperbar/Views/IObservableCollectionViewModel.cs b/Hyperbar/Views/IObservableCollectionViewModel.cs index ab316e7..2e1df9c 100644 --- a/Hyperbar/Views/IObservableCollectionViewModel.cs +++ b/Hyperbar/Views/IObservableCollectionViewModel.cs @@ -7,5 +7,4 @@ public interface IObservableCollectionViewModel : IList, IList, IReadOnlyList, - INotifyCollectionChanged, - IInitializer; + INotifyCollectionChanged; \ No newline at end of file diff --git a/Hyperbar/Views/IViewModelInitialization.cs b/Hyperbar/Views/IViewModelInitialization.cs new file mode 100644 index 0000000..0faceab --- /dev/null +++ b/Hyperbar/Views/IViewModelInitialization.cs @@ -0,0 +1,10 @@ +using System.Windows.Input; + +namespace Hyperbar; + +public interface IViewModelInitialization +{ + ICommand Initialize { get; } + + Task InitializeAsync(); +} diff --git a/Hyperbar/Views/IWidgetComponentViewModel.cs b/Hyperbar/Views/IWidgetComponentViewModel.cs index cdba8b4..6cf934f 100644 --- a/Hyperbar/Views/IWidgetComponentViewModel.cs +++ b/Hyperbar/Views/IWidgetComponentViewModel.cs @@ -1,3 +1,3 @@ namespace Hyperbar; -public interface IWidgetComponentViewModel; \ No newline at end of file +public interface IWidgetComponentViewModel : IDisposable; \ No newline at end of file diff --git a/Hyperbar/Views/IWidgetViewModel.cs b/Hyperbar/Views/IWidgetViewModel.cs index f877417..ff92d9f 100644 --- a/Hyperbar/Views/IWidgetViewModel.cs +++ b/Hyperbar/Views/IWidgetViewModel.cs @@ -1,3 +1,3 @@ namespace Hyperbar; -public interface IWidgetViewModel; \ No newline at end of file +public interface IWidgetViewModel : IDisposable; \ No newline at end of file diff --git a/Hyperbar/Views/ObservableCollectionViewModel.cs b/Hyperbar/Views/ObservableCollectionViewModel.cs index bee55df..ed97c53 100644 --- a/Hyperbar/Views/ObservableCollectionViewModel.cs +++ b/Hyperbar/Views/ObservableCollectionViewModel.cs @@ -1,10 +1,8 @@ using CommunityToolkit.Mvvm.ComponentModel; -using CommunityToolkit.Mvvm.Input; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; using System.Reactive.Disposables; -using System.Windows.Input; namespace Hyperbar; @@ -12,22 +10,21 @@ public partial class ObservableCollectionViewModel : ObservableObject, IObservableCollectionViewModel, INotificationHandler>, - INotificationHandler> + INotificationHandler>, + IDisposable + where TItem : + IDisposable { public ObservableCollection collection = []; - private readonly SynchronizationContext? context; - private readonly IDisposer disposer; private readonly IViewModelEnumerator? enumerator; - private readonly IServiceFactory serviceFactory; public ObservableCollectionViewModel(IServiceFactory serviceFactory, IMediator mediator, IDisposer disposer) { - context = SynchronizationContext.Current; - - this.serviceFactory = serviceFactory; - this.disposer = disposer; + ServiceFactory = serviceFactory; + Mediator = mediator; + Disposer = disposer; mediator.Subscribe(this); @@ -39,16 +36,16 @@ public partial class ObservableCollectionViewModel : IDisposer disposer, IViewModelEnumerator enumerator) { - context = SynchronizationContext.Current; + ServiceFactory = serviceFactory; + Mediator = mediator; + Disposer = disposer; - this.serviceFactory = serviceFactory; - this.disposer = disposer; this.enumerator = enumerator; mediator.Subscribe(this); collection.CollectionChanged += OnCollectionChanged; - + if (enumerator is not null) { foreach (TItem? item in enumerator.Next()) @@ -66,10 +63,9 @@ public partial class ObservableCollectionViewModel : IDisposer disposer, IEnumerable items) { - context = SynchronizationContext.Current; - - this.serviceFactory = serviceFactory; - this.disposer = disposer; + ServiceFactory = serviceFactory; + Mediator = mediator; + Disposer = disposer; mediator.Subscribe(this); @@ -82,10 +78,6 @@ public partial class ObservableCollectionViewModel : public int Count => collection.Count; - public ICommand Initialize => new AsyncRelayCommand(InitializeAsync); - - public bool Initialized { get; private set; } - bool IList.IsFixedSize => false; bool ICollection.IsReadOnly => false; @@ -96,8 +88,14 @@ public partial class ObservableCollectionViewModel : object ICollection.SyncRoot => this; + protected IDisposer Disposer { get; private set; } + protected IList Items => collection; + protected IMediator Mediator { get; private set; } + + protected IServiceFactory ServiceFactory { get; private set; } + public TItem this[int index] { get => collection[index]; @@ -129,7 +127,7 @@ public partial class ObservableCollectionViewModel : public TItem Add() { - TItem? item = serviceFactory.Create(); + TItem? item = ServiceFactory.Create(); Add(item); return item; @@ -138,7 +136,7 @@ public partial class ObservableCollectionViewModel : public TItem Add(params object?[] parameters) where T : TItem { - T? item = serviceFactory.Create(parameters); + T? item = ServiceFactory.Create(parameters); Add(item); return item; @@ -148,7 +146,7 @@ public partial class ObservableCollectionViewModel : where T : TItem { - T? item = serviceFactory.Create(); + T? item = ServiceFactory.Create(); Add(item); return item; @@ -156,15 +154,6 @@ public partial class ObservableCollectionViewModel : public void Add(TItem item) { - disposer.Add(this, item); - disposer.Add(item, Disposable.Create(item, args => - { - if (Contains(args)) - { - Remove(args); - } - })); - int index = collection.Count; InsertItem(index, item); } @@ -208,7 +197,9 @@ public partial class ObservableCollectionViewModel : void ICollection.CopyTo(Array array, int index) => collection.CopyTo((TItem[])array, index); - public IEnumerator GetEnumerator() => + public void Dispose() => Disposer.Dispose(this); + + public IEnumerator GetEnumerator() => collection.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => @@ -245,23 +236,13 @@ public partial class ObservableCollectionViewModel : return ValueTask.CompletedTask; } - public int IndexOf(TItem item) => collection.IndexOf(item); + public int IndexOf(TItem item) => + collection.IndexOf(item); int IList.IndexOf(object? value) => IsCompatibleObject(value) ? IndexOf((TItem)value!) : -1; - public Task InitializeAsync() - { - if (Initialized) - { - return Task.CompletedTask; - } - - Initialized = true; - return Task.CompletedTask; - } - public void Insert(int index, TItem item) => InsertItem(index, item); @@ -279,6 +260,7 @@ public partial class ObservableCollectionViewModel : int index = collection.IndexOf(item); if (index < 0) return false; + Disposer.Remove(this, item); RemoveItem(index); return true; @@ -299,7 +281,20 @@ public partial class ObservableCollectionViewModel : collection.Clear(); protected virtual void InsertItem(int index, - TItem value) => collection.Insert(index, value); + TItem value) + { + Disposer.Add(this, Disposable.Create(() => + { + Remove(value); + })); + + Disposer.Add(value, Disposable.Create(() => + { + Remove(value); + })); + + collection.Insert(index, value); + } protected virtual void RemoveItem(int index) => collection.RemoveAt(index); @@ -317,4 +312,4 @@ public partial class ObservableCollectionViewModel : public class ObservableCollectionViewModel(IServiceFactory serviceFactory, IMediator mediator, IDisposer disposer) : - ObservableCollectionViewModel(serviceFactory, mediator, disposer); \ No newline at end of file + ObservableCollectionViewModel(serviceFactory, mediator, disposer); \ No newline at end of file diff --git a/Hyperbar/Views/WidgetComponentViewModel.cs b/Hyperbar/Views/WidgetComponentViewModel.cs index 651c032..23b7850 100644 --- a/Hyperbar/Views/WidgetComponentViewModel.cs +++ b/Hyperbar/Views/WidgetComponentViewModel.cs @@ -18,6 +18,7 @@ public partial class WidgetComponentViewModel : Guid id = default) : base(serviceFactory, mediator, disposer, items) { this.id = id; + TemplateFactory = templateFactory; } @@ -28,6 +29,7 @@ public partial class WidgetComponentViewModel : Guid id = default) : base(serviceFactory, mediator, disposer) { this.id = id; + TemplateFactory = templateFactory; }