Damn garbage collector
This commit is contained in:
@@ -8,19 +8,13 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<None Remove="MediaControllerView.xaml" />
|
||||
<None Remove="MediaControllerWidgetView.xaml" />
|
||||
<None Remove="MediaInformationView.xaml" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CommunityToolkit.WinUI" Version="7.1.2" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls.Primitives" Version="7.1.2" />
|
||||
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.5.231202003-experimental1" />
|
||||
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25936-preview" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="2.0.9" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="8.0.0" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public record FowardRequest : INotification;
|
||||
public record Backward : INotification;
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public record Play : INotification;
|
||||
public record Foward : INotification;
|
||||
@@ -0,0 +1,92 @@
|
||||
using Windows.Media.Control;
|
||||
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public class MediaController :
|
||||
INotificationHandler<Play>,
|
||||
INotificationHandler<Pause>,
|
||||
INotificationHandler<Request<Playback>>,
|
||||
INotificationHandler<Request<MediaInformation>>,
|
||||
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<Playback> notification,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
await mediator.PublishAsync(new Changed<Playback>(), cancellationToken);
|
||||
}
|
||||
|
||||
public async ValueTask Handle(Request<MediaInformation> _,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
using (await asyncLock)
|
||||
{
|
||||
try
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties = await session.TryGetMediaPropertiesAsync();
|
||||
await mediator.PublishAsync(new Changed<MediaInformation>(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<MediaInformation>(new MediaInformation(mediaProperties.Title,
|
||||
mediaProperties.Artist)));
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnPlaybackInfoChanged(GlobalSystemMediaTransportControlsSession sender,
|
||||
PlaybackInfoChangedEventArgs args)
|
||||
{
|
||||
await mediator.PublishAsync(new Changed<Playback>());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public class MediaControllerHandler(IMediator mediator,
|
||||
IServiceScopeProvider<MediaController> scopeProvider,
|
||||
ICache<MediaController, MediaControllerViewModel> cache) :
|
||||
INotificationHandler<Created<MediaController>>
|
||||
{
|
||||
public async ValueTask Handle(Created<MediaController> 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<IFactory<MediaController, MediaControllerViewModel?>>()
|
||||
is IFactory<MediaController, MediaControllerViewModel?> factory)
|
||||
{
|
||||
if (factory.Create(mediaController) is MediaControllerViewModel mediaControllerViewModel)
|
||||
{
|
||||
cache.Add(mediaController, mediaControllerViewModel);
|
||||
await mediator.PublishAsync(new Created<MediaControllerViewModel>(mediaControllerViewModel),
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
using System.Diagnostics;
|
||||
using Windows.Media.Control;
|
||||
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
public class MediaControllerManager(IMediator mediator,
|
||||
IFactory<GlobalSystemMediaTransportControlsSession, MediaController> factory,
|
||||
IDispatcher dispatcher,
|
||||
IDisposer disposer) :
|
||||
IInitializer
|
||||
{
|
||||
private readonly AsyncLock asyncLock = new();
|
||||
private readonly List<KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>> cache = [];
|
||||
private GlobalSystemMediaTransportControlsSessionManager? mediaTransportControlsSessionManager;
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
mediaTransportControlsSessionManager = await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
|
||||
mediaTransportControlsSessionManager.SessionsChanged += OnSessionsChanged;
|
||||
|
||||
IReadOnlyList<GlobalSystemMediaTransportControlsSession> 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>(mediaController));
|
||||
Debug.WriteLine("Added " + session.SourceAppUserModelId + " " + session);
|
||||
|
||||
cache.Add(new KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>(session, mediaController));
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender,
|
||||
SessionsChangedEventArgs args)
|
||||
{
|
||||
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
|
||||
sender.GetSessions();
|
||||
|
||||
using (await asyncLock)
|
||||
{
|
||||
foreach (KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public class MediaControllerViewModelFactory(IServiceFactory service) :
|
||||
IFactory<MediaController, MediaControllerViewModel?>
|
||||
{
|
||||
public MediaControllerViewModel? Create(MediaController value)
|
||||
{
|
||||
if (service.Create<MediaControllerViewModel>()
|
||||
is MediaControllerViewModel widgetComponentViewModel)
|
||||
{
|
||||
return widgetComponentViewModel;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
+4
-4
@@ -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<MediaController, IServiceScope>()
|
||||
.AddTransient<IFactory<GlobalSystemMediaTransportControlsSession, MediaController?>, MediaControllerFactory>()
|
||||
.AddHandler<MediaControllerHandler>()
|
||||
.AddTransient<IFactory<MediaControllerViewModel?>, MediaControllerViewModelFactory>()
|
||||
.AddCache<MediaControllerViewModel>()
|
||||
.AddTransient<IFactory<MediaController, MediaControllerViewModel?>, MediaControllerViewModelFactory>()
|
||||
.AddCache<MediaController, MediaControllerViewModel>()
|
||||
.AddContentTemplate<MediaControllerViewModel, MediaControllerView>()
|
||||
.AddContentTemplate<MediaInformationViewModel, MediaInformationView>();
|
||||
.AddContentTemplate<MediaInformationViewModel, MediaInformationView>()
|
||||
.AddContentTemplate<MediaButtonViewModel, MediaButtonView>();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public record MediaInformation(string Title, string Description);
|
||||
|
||||
|
||||
+1
-3
@@ -1,5 +1,3 @@
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public record Media;
|
||||
|
||||
|
||||
public record Play : INotification;
|
||||
+1
-1
@@ -1,3 +1,3 @@
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public record BackwardRequest : INotification;
|
||||
public record Playback : INotification;
|
||||
@@ -1,34 +0,0 @@
|
||||
using Windows.Media.Control;
|
||||
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public class MediaController :
|
||||
INotificationHandler<Play>,
|
||||
INotificationHandler<Pause>
|
||||
{
|
||||
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<Media>());
|
||||
}
|
||||
|
||||
public async ValueTask Handle(Play notification, CancellationToken cancellationToken) =>
|
||||
await session.TryPlayAsync();
|
||||
|
||||
public async ValueTask Handle(Pause notification, CancellationToken cancellationToken) =>
|
||||
await session.TryPauseAsync();
|
||||
}
|
||||
@@ -1,28 +0,0 @@
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public class MediaControllerHandler(IMediator mediator,
|
||||
IServiceScopeProvider<MediaController> scopeProvider) :
|
||||
INotificationHandler<Created<MediaController>>
|
||||
{
|
||||
public async ValueTask Handle(Created<MediaController> notification,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (scopeProvider.TryGet(notification.Value, out IServiceScope? serviceScope))
|
||||
{
|
||||
if (serviceScope is not null)
|
||||
{
|
||||
if (serviceScope.ServiceProvider.GetService<IFactory<MediaControllerViewModel?>>()
|
||||
is IFactory<MediaControllerViewModel?> factory)
|
||||
{
|
||||
if (factory.Create() is MediaControllerViewModel mediaControllerViewModel)
|
||||
{
|
||||
await mediator.PublishAsync(new Created<MediaControllerViewModel>(mediaControllerViewModel),
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,58 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Windows.Media.Control;
|
||||
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
public class MediaControllerManager(IMediator mediator,
|
||||
IFactory<GlobalSystemMediaTransportControlsSession, MediaController> factory) :
|
||||
IInitializer
|
||||
{
|
||||
private readonly List<KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>> cachedSessions = [];
|
||||
|
||||
public async Task InitializeAsync()
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionManager mediaTransportControlsSessionManager =
|
||||
await GlobalSystemMediaTransportControlsSessionManager.RequestAsync();
|
||||
mediaTransportControlsSessionManager.SessionsChanged += OnSessionsChanged;
|
||||
|
||||
IReadOnlyList<GlobalSystemMediaTransportControlsSession> 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>(mediaController));
|
||||
cachedSessions.Add(new KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController>(session, mediaController));
|
||||
}
|
||||
}
|
||||
|
||||
private async void OnSessionsChanged(GlobalSystemMediaTransportControlsSessionManager sender,
|
||||
SessionsChangedEventArgs args)
|
||||
{
|
||||
IReadOnlyList<GlobalSystemMediaTransportControlsSession> sessions =
|
||||
sender.GetSessions();
|
||||
|
||||
foreach (KeyValuePair<GlobalSystemMediaTransportControlsSession, MediaController> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,16 +0,0 @@
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public class MediaControllerViewModelFactory(IServiceFactory service, ICache<MediaControllerViewModel> cache) :
|
||||
IFactory<MediaControllerViewModel?>
|
||||
{
|
||||
public MediaControllerViewModel? Create()
|
||||
{
|
||||
if (service.Create<MediaControllerViewModel>() is MediaControllerViewModel widgetComponentViewModel)
|
||||
{
|
||||
cache.Add(widgetComponentViewModel);
|
||||
return widgetComponentViewModel;
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="Hyperbar.Windows.MediaController.MediaButtonView"
|
||||
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">
|
||||
<UserControl.Resources>
|
||||
<SolidColorBrush x:Key="ButtonBackground" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ButtonBorderBrush" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ButtonBorderBrushPointerOver" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ButtonBorderBrushPressed" Color="Transparent" />
|
||||
<SolidColorBrush x:Key="ButtonBorderBrushDisabled" Color="Transparent" />
|
||||
<Thickness x:Key="ButtonPadding">0</Thickness>
|
||||
<x:Double x:Key="ButtonWidth">40</x:Double>
|
||||
<x:Double x:Key="ButtonHeight">40</x:Double>
|
||||
</UserControl.Resources>
|
||||
<Button
|
||||
Width="{StaticResource ButtonWidth}"
|
||||
Height="{StaticResource ButtonHeight}"
|
||||
Padding="{StaticResource ButtonPadding}"
|
||||
Command="{Binding Click}"
|
||||
Content="{Binding Icon}"
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
FontSize="16"
|
||||
ToolTipService.ToolTip="{Binding Text}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactions:EventTriggerBehavior EventName="Loaded">
|
||||
<interactions:InvokeCommandAction Command="{Binding Initialize}" />
|
||||
</interactions:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</Button>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,9 @@
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
|
||||
namespace Hyperbar.Windows.MediaController;
|
||||
|
||||
public sealed partial class MediaButtonView :
|
||||
UserControl
|
||||
{
|
||||
public MediaButtonView() => InitializeComponent();
|
||||
}
|
||||
@@ -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<Request<Playback>>();
|
||||
}
|
||||
+1
-1
@@ -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">
|
||||
<Grid Width="400">
|
||||
<Grid Width="400" Background="red">
|
||||
<ItemsControl
|
||||
HorizontalAlignment="Center"
|
||||
ItemTemplateSelector="{Binding Converter={ui:DataTemplateConverter}}"
|
||||
+4
-4
@@ -14,10 +14,10 @@ public class MediaControllerViewModel :
|
||||
TemplateFactory = templateFactory;
|
||||
|
||||
Add<MediaInformationViewModel>();
|
||||
Add<WidgetButtonViewModel>("Backward", "\uEB9E");
|
||||
Add<WidgetButtonViewModel>("Play", "\uE768", new RelayCommand(async () => await mediator.SendAsync(new Play())));
|
||||
Add<WidgetButtonViewModel>("Pause", "\uE769", new RelayCommand(async () => await mediator.PublishAsync(new Pause())));
|
||||
Add<WidgetButtonViewModel>("Forward", "\uEB9D");
|
||||
Add<MediaButtonViewModel>("Backward", "\uEB9E");
|
||||
Add<MediaButtonViewModel>("Play", "\uE768", new RelayCommand(async () => await mediator.PublishAsync<Play>()));
|
||||
Add<MediaButtonViewModel>("Pause", "\uE769", new RelayCommand(async () => await mediator.PublishAsync<Pause>()));
|
||||
Add<MediaButtonViewModel>("Forward", "\uEB9D");
|
||||
}
|
||||
|
||||
public ITemplateFactory TemplateFactory { get; set; }
|
||||
+8
-1
@@ -2,7 +2,9 @@
|
||||
<UserControl
|
||||
x:Class="Hyperbar.Windows.MediaController.MediaInformationView"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:interactions="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="40" />
|
||||
@@ -20,6 +22,11 @@
|
||||
Opacity="0.7"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Description}" />
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactions:EventTriggerBehavior EventName="Loaded">
|
||||
<interactions:InvokeCommandAction Command="{Binding Initialize}" />
|
||||
</interactions:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -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<Changed<MediaInformation>>
|
||||
{
|
||||
[ObservableProperty]
|
||||
private string? description;
|
||||
|
||||
[ObservableProperty]
|
||||
private string? title;
|
||||
|
||||
public ICommand Initialize =>
|
||||
new AsyncRelayCommand(InitializeAsync);
|
||||
|
||||
public ValueTask Handle(Changed<MediaInformation> 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<Request<MediaInformation>>();
|
||||
}
|
||||
@@ -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">
|
||||
<ItemsControl ItemTemplateSelector="{Binding Converter={ui:DataTemplateConverter}}" ItemsSource="{Binding}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
@@ -12,10 +10,5 @@
|
||||
<StackPanel Orientation="Horizontal" Spacing="8" />
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactions:EventTriggerBehavior EventName="Loaded">
|
||||
<interactions:InvokeCommandAction Command="{Binding Initialize}" />
|
||||
</interactions:EventTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</ItemsControl>
|
||||
</UserControl>
|
||||
@@ -13,7 +13,7 @@ public static class IServiceCollectionExtensions
|
||||
where TKey :
|
||||
notnull
|
||||
{
|
||||
services.AddSingleton<ICache<TKey, TValue>, Cache<TKey, TValue>>();
|
||||
services.AddScoped<ICache<TKey, TValue>, Cache<TKey, TValue>>();
|
||||
services.AddTransient(provider => provider.GetService<ICache<TKey, TValue>>()!.Select(x => x.Value));
|
||||
|
||||
return services;
|
||||
|
||||
@@ -31,18 +31,20 @@ public class Cache<TValue>(IDisposer disposer) :
|
||||
public class Cache<TKey, TValue>(IDisposer disposer) :
|
||||
ICache<TKey, TValue>
|
||||
where TKey : notnull
|
||||
where TValue : notnull
|
||||
{
|
||||
private readonly ConcurrentDictionary<TKey, TValue> 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();
|
||||
|
||||
@@ -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<AsyncLock> GetAwaiter() => LockAsync().GetAwaiter();
|
||||
|
||||
private async Task<AsyncLock> LockAsync()
|
||||
{
|
||||
await semaphore.WaitAsync();
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class Disposer :
|
||||
IDisposer
|
||||
{
|
||||
private readonly ConditionalWeakTable<object, CompositeDisposable> subjects = [];
|
||||
private readonly ConcurrentDictionary<object, CompositeDisposable> 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<IDisposable>())
|
||||
{
|
||||
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);
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
namespace Hyperbar;
|
||||
|
||||
public record Request<TValue> : INotification;
|
||||
@@ -0,0 +1,6 @@
|
||||
namespace Hyperbar;
|
||||
|
||||
public interface IDispatcher
|
||||
{
|
||||
Task InvokeAsync(Action action);
|
||||
}
|
||||
@@ -7,6 +7,11 @@ public interface IMediator
|
||||
where TNotification :
|
||||
INotification;
|
||||
|
||||
ValueTask PublishAsync<TNotification>(CancellationToken cancellationToken = default)
|
||||
where TNotification :
|
||||
INotification,
|
||||
new();
|
||||
|
||||
ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> request,
|
||||
CancellationToken cancellationToken = default);
|
||||
|
||||
|
||||
@@ -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<TNotification>(CancellationToken cancellationToken = default)
|
||||
where TNotification :
|
||||
INotification,
|
||||
new() => PublishAsync(new TNotification(), cancellationToken);
|
||||
|
||||
public ValueTask<TResponse> SendAsync<TResponse>(IRequest<TResponse> request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
|
||||
@@ -7,5 +7,4 @@ public interface IObservableCollectionViewModel<TItem> :
|
||||
IList<TItem>,
|
||||
IList,
|
||||
IReadOnlyList<TItem>,
|
||||
INotifyCollectionChanged,
|
||||
IInitializer;
|
||||
INotifyCollectionChanged;
|
||||
@@ -0,0 +1,10 @@
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace Hyperbar;
|
||||
|
||||
public interface IViewModelInitialization
|
||||
{
|
||||
ICommand Initialize { get; }
|
||||
|
||||
Task InitializeAsync();
|
||||
}
|
||||
@@ -1,3 +1,3 @@
|
||||
namespace Hyperbar;
|
||||
|
||||
public interface IWidgetComponentViewModel;
|
||||
public interface IWidgetComponentViewModel : IDisposable;
|
||||
@@ -1,3 +1,3 @@
|
||||
namespace Hyperbar;
|
||||
|
||||
public interface IWidgetViewModel;
|
||||
public interface IWidgetViewModel : IDisposable;
|
||||
@@ -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<TItem> :
|
||||
ObservableObject,
|
||||
IObservableCollectionViewModel<TItem>,
|
||||
INotificationHandler<Removed<TItem>>,
|
||||
INotificationHandler<Created<TItem>>
|
||||
INotificationHandler<Created<TItem>>,
|
||||
IDisposable
|
||||
where TItem :
|
||||
IDisposable
|
||||
{
|
||||
public ObservableCollection<TItem> collection = [];
|
||||
private readonly SynchronizationContext? context;
|
||||
private readonly IDisposer disposer;
|
||||
private readonly IViewModelEnumerator<TItem>? 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<TItem> :
|
||||
IDisposer disposer,
|
||||
IViewModelEnumerator<TItem> 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<TItem> :
|
||||
IDisposer disposer,
|
||||
IEnumerable<TItem> 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<TItem> :
|
||||
|
||||
public int Count => collection.Count;
|
||||
|
||||
public ICommand Initialize => new AsyncRelayCommand(InitializeAsync);
|
||||
|
||||
public bool Initialized { get; private set; }
|
||||
|
||||
bool IList.IsFixedSize => false;
|
||||
|
||||
bool ICollection<TItem>.IsReadOnly => false;
|
||||
@@ -96,8 +88,14 @@ public partial class ObservableCollectionViewModel<TItem> :
|
||||
|
||||
object ICollection.SyncRoot => this;
|
||||
|
||||
protected IDisposer Disposer { get; private set; }
|
||||
|
||||
protected IList<TItem> 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<TItem> :
|
||||
|
||||
public TItem Add()
|
||||
{
|
||||
TItem? item = serviceFactory.Create<TItem>();
|
||||
TItem? item = ServiceFactory.Create<TItem>();
|
||||
|
||||
Add(item);
|
||||
return item;
|
||||
@@ -138,7 +136,7 @@ public partial class ObservableCollectionViewModel<TItem> :
|
||||
public TItem Add<T>(params object?[] parameters)
|
||||
where T : TItem
|
||||
{
|
||||
T? item = serviceFactory.Create<T>(parameters);
|
||||
T? item = ServiceFactory.Create<T>(parameters);
|
||||
Add(item);
|
||||
|
||||
return item;
|
||||
@@ -148,7 +146,7 @@ public partial class ObservableCollectionViewModel<TItem> :
|
||||
where T :
|
||||
TItem
|
||||
{
|
||||
T? item = serviceFactory.Create<T>();
|
||||
T? item = ServiceFactory.Create<T>();
|
||||
Add(item);
|
||||
|
||||
return item;
|
||||
@@ -156,15 +154,6 @@ public partial class ObservableCollectionViewModel<TItem> :
|
||||
|
||||
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<TItem> :
|
||||
void ICollection.CopyTo(Array array, int index) =>
|
||||
collection.CopyTo((TItem[])array, index);
|
||||
|
||||
public IEnumerator<TItem> GetEnumerator() =>
|
||||
public void Dispose() => Disposer.Dispose(this);
|
||||
|
||||
public IEnumerator<TItem> GetEnumerator() =>
|
||||
collection.GetEnumerator();
|
||||
|
||||
IEnumerator IEnumerable.GetEnumerator() =>
|
||||
@@ -245,23 +236,13 @@ public partial class ObservableCollectionViewModel<TItem> :
|
||||
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<TItem> :
|
||||
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<TItem> :
|
||||
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<TItem> :
|
||||
public class ObservableCollectionViewModel(IServiceFactory serviceFactory,
|
||||
IMediator mediator,
|
||||
IDisposer disposer) :
|
||||
ObservableCollectionViewModel<object>(serviceFactory, mediator, disposer);
|
||||
ObservableCollectionViewModel<IDisposable>(serviceFactory, mediator, disposer);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user