From 43f96fd4f05c112e38ebc5340afc2a533af7b4de Mon Sep 17 00:00:00 2001 From: TheXamlGuy Date: Sun, 4 Feb 2024 22:06:55 +0000 Subject: [PATCH] Initial navigaiton work; opening Windows --- Hyperbar.UI.Windows/WindowHandler.cs | 18 +++++ Hyperbar.Widget/WidgetExtensionEnumerator.cs | 4 +- Hyperbar.Windows/App.xaml.cs | 3 +- Hyperbar.Windows/SecondaryViewModel.cs | 4 +- Hyperbar.Windows/SettingsButtonView.xaml | 1 + Hyperbar.Windows/SettingsButtonView.xaml.cs | 7 +- Hyperbar.Windows/SettingsButtonViewModel.cs | 27 +++++-- Hyperbar.Windows/SettingsView.xaml | 6 +- Hyperbar.Windows/SettingsView.xaml.cs | 5 +- .../IServiceCollectionExtensions.cs | 73 +++++++++++++------ Hyperbar/Lifecycles/INavigationDescriptor.cs | 7 ++ Hyperbar/Lifecycles/INavigationHandler.cs | 7 ++ Hyperbar/Lifecycles/Navigate.cs | 8 ++ Hyperbar/Lifecycles/NavigateHandler.cs | 53 ++++++++++++++ Hyperbar/Lifecycles/NavigationDescriptor.cs | 8 ++ Hyperbar/Lifecycles/Remove.cs | 35 +-------- Hyperbar/Mediators/IMediator.cs | 9 ++- Hyperbar/Mediators/INotificationHandler.cs | 2 +- Hyperbar/Mediators/Mediator.cs | 45 ++++++++---- 19 files changed, 228 insertions(+), 94 deletions(-) create mode 100644 Hyperbar.UI.Windows/WindowHandler.cs create mode 100644 Hyperbar/Lifecycles/INavigationDescriptor.cs create mode 100644 Hyperbar/Lifecycles/INavigationHandler.cs create mode 100644 Hyperbar/Lifecycles/Navigate.cs create mode 100644 Hyperbar/Lifecycles/NavigateHandler.cs create mode 100644 Hyperbar/Lifecycles/NavigationDescriptor.cs diff --git a/Hyperbar.UI.Windows/WindowHandler.cs b/Hyperbar.UI.Windows/WindowHandler.cs new file mode 100644 index 0000000..9919f0f --- /dev/null +++ b/Hyperbar.UI.Windows/WindowHandler.cs @@ -0,0 +1,18 @@ +using Microsoft.UI.Xaml; + +namespace Hyperbar.UI.Windows; + +public class WindowHandler : + INavigationHandler +{ + public Task Handle(Navigate args, + CancellationToken cancellationToken) + { + if (args.Template is Window window) + { + window.Activate(); + } + + return Task.CompletedTask; + } +} diff --git a/Hyperbar.Widget/WidgetExtensionEnumerator.cs b/Hyperbar.Widget/WidgetExtensionEnumerator.cs index 7cfeb0b..cdf72e3 100644 --- a/Hyperbar.Widget/WidgetExtensionEnumerator.cs +++ b/Hyperbar.Widget/WidgetExtensionEnumerator.cs @@ -9,8 +9,8 @@ public class WidgetExtensionEnumerator(IFactory factory, IMediator mediator) : INotificationHandler> { - public Task Handle(Enumerate notification, - CancellationToken cancellationToken) + public Task Handle(Enumerate args, + CancellationToken cancellationToken = default) { string extensionsDirectory = Path.Combine(hostEnvironment.ContentRootPath, "Extensions"); if (Directory.Exists(extensionsDirectory)) diff --git a/Hyperbar.Windows/App.xaml.cs b/Hyperbar.Windows/App.xaml.cs index 871ff71..4c38eb6 100644 --- a/Hyperbar.Windows/App.xaml.cs +++ b/Hyperbar.Windows/App.xaml.cs @@ -8,7 +8,6 @@ using Microsoft.UI.Dispatching; using Microsoft.UI.Xaml; using System.Reflection; using Hyperbar.Widget.Windows; -using Microsoft.UI.Xaml.Markup; namespace Hyperbar.Windows; @@ -46,6 +45,8 @@ public partial class App : args.Placement = DesktopApplicationBarPlacemenet.Top; }); + services.AddNavigationHandler(); + services.AddSingleton(); services.AddContentTemplate(); services.AddContentTemplate(); diff --git a/Hyperbar.Windows/SecondaryViewModel.cs b/Hyperbar.Windows/SecondaryViewModel.cs index 7c81b46..eb78c8a 100644 --- a/Hyperbar.Windows/SecondaryViewModel.cs +++ b/Hyperbar.Windows/SecondaryViewModel.cs @@ -15,9 +15,9 @@ public partial class SecondaryViewModel : IMediator mediator, IDisposer disposer, int index) : base(serviceFactory, mediator, disposer) - { + { + TemplateFactory = templateFactory; this.index = index; - this.TemplateFactory = templateFactory; Add(); } diff --git a/Hyperbar.Windows/SettingsButtonView.xaml b/Hyperbar.Windows/SettingsButtonView.xaml index 1a6782e..e99f889 100644 --- a/Hyperbar.Windows/SettingsButtonView.xaml +++ b/Hyperbar.Windows/SettingsButtonView.xaml @@ -18,6 +18,7 @@ Width="{ThemeResource ButtonWidth}" Height="{ThemeResource ButtonHeight}" Padding="{ThemeResource ButtonPadding}" + Command="{x:Bind ViewModel.InvokeCommand}" Content="" FontFamily="{ThemeResource SymbolThemeFontFamily}" FontSize="16" /> diff --git a/Hyperbar.Windows/SettingsButtonView.xaml.cs b/Hyperbar.Windows/SettingsButtonView.xaml.cs index e8e3380..7b77458 100644 --- a/Hyperbar.Windows/SettingsButtonView.xaml.cs +++ b/Hyperbar.Windows/SettingsButtonView.xaml.cs @@ -2,9 +2,12 @@ using Microsoft.UI.Xaml.Controls; namespace Hyperbar.Windows; -public sealed partial class SettingsButtonView : +public partial class SettingsButtonView : UserControl { public SettingsButtonView() => - this.InitializeComponent(); + InitializeComponent(); + + protected SettingsButtonViewModel ViewModel => + (SettingsButtonViewModel)DataContext; } diff --git a/Hyperbar.Windows/SettingsButtonViewModel.cs b/Hyperbar.Windows/SettingsButtonViewModel.cs index 3e734ae..d2e1ad7 100644 --- a/Hyperbar.Windows/SettingsButtonViewModel.cs +++ b/Hyperbar.Windows/SettingsButtonViewModel.cs @@ -1,11 +1,24 @@ -namespace Hyperbar.Windows; +using CommunityToolkit.Mvvm.ComponentModel; +using CommunityToolkit.Mvvm.Input; -public class SettingsButtonViewModel(ITemplateFactory templateFactory, - IServiceFactory serviceFactory, - IMediator mediator, - IDisposer disposer) : - ObservableViewModel(serviceFactory, mediator, disposer), +namespace Hyperbar.Windows; + +public partial class SettingsButtonViewModel : + ObservableViewModel, ITemplatedViewModel { - public ITemplateFactory TemplateFactory => templateFactory; + [ObservableProperty] + private IRelayCommand? invokeCommand; + + public SettingsButtonViewModel(ITemplateFactory templateFactory, + IServiceFactory serviceFactory, + IMediator mediator, + IDisposer disposer) : base(serviceFactory, mediator, disposer) + { + TemplateFactory = templateFactory; + InvokeCommand = new AsyncRelayCommand(async () => + await mediator.PublishAsync(new Navigate("Settings"))); + } + + public ITemplateFactory TemplateFactory { get; } } diff --git a/Hyperbar.Windows/SettingsView.xaml b/Hyperbar.Windows/SettingsView.xaml index 4da9db5..faf18d4 100644 --- a/Hyperbar.Windows/SettingsView.xaml +++ b/Hyperbar.Windows/SettingsView.xaml @@ -1,7 +1,7 @@ - - - + + diff --git a/Hyperbar.Windows/SettingsView.xaml.cs b/Hyperbar.Windows/SettingsView.xaml.cs index d3b2d3b..d91e98f 100644 --- a/Hyperbar.Windows/SettingsView.xaml.cs +++ b/Hyperbar.Windows/SettingsView.xaml.cs @@ -1,9 +1,10 @@ +using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Controls; namespace Hyperbar.Windows { - public sealed partial class SettingsView : - UserControl + public sealed partial class SettingsView : + Window { public SettingsView() => InitializeComponent(); diff --git a/Hyperbar/Extensions/IServiceCollectionExtensions.cs b/Hyperbar/Extensions/IServiceCollectionExtensions.cs index 87bb10d..8841216 100644 --- a/Hyperbar/Extensions/IServiceCollectionExtensions.cs +++ b/Hyperbar/Extensions/IServiceCollectionExtensions.cs @@ -3,6 +3,8 @@ using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders.Physical; using Microsoft.Extensions.Hosting; +using System.Diagnostics.CodeAnalysis; +using System.Net.Mime; using System.Text.Json; namespace Hyperbar; @@ -128,6 +130,26 @@ public static class IServiceCollectionExtensions return services; } + public static IServiceCollection AddNavigationHandler(this IServiceCollection services) + where THandler : + INavigationHandler, + IHandler + { + Type? contract = typeof(THandler).GetInterfaces() + .FirstOrDefault(t => t.Name == typeof(INavigationHandler<>).Name); + + if (contract?.GetGenericArguments() is { Length: 1 } arguments) + { + services.AddTransient(provider => new NavigationDescriptor + { + Type = arguments[0] + }); + } + + services.AddHandler(); + return services; + } + public static IServiceCollection AddContentTemplate(this IServiceCollection services, object? key = null) { @@ -163,52 +185,59 @@ public static class IServiceCollectionExtensions services.AddSingleton(); + services.AddHandler(); + return services; } - public static IServiceCollection AddHandler(this IServiceCollection services, + public static IServiceCollection AddHandler( + this IServiceCollection services, ServiceLifetime lifetime = ServiceLifetime.Transient) - where THandler : - IHandler + where THandler : IHandler { if (typeof(THandler).GetInterfaces() is { } contracts) { foreach (Type contract in contracts) { - if (contract.Name == typeof(INotificationHandler<>).Name) + if (contract.Name == typeof(INotificationHandler<>).Name && + contract.GetGenericArguments() is { Length: 1 } notificationArguments) { - if (contract.GetGenericArguments() is { Length: 1 } arguments) - { - Type notificationType = arguments[0]; - services.Add(new ServiceDescriptor(typeof(INotificationHandler<>).MakeGenericType(notificationType), typeof(THandler), lifetime)); - } + Type notificationType = notificationArguments[0]; + services.Add(new ServiceDescriptor( + typeof(INotificationHandler<>).MakeGenericType(notificationType), + typeof(THandler), + lifetime)); } - if (contract.Name == typeof(IHandler<,>).Name) + if (contract.Name == typeof(IHandler<,>).Name && + contract.GetGenericArguments() is { Length: 2 } handlerArguments) { - if (contract.GetGenericArguments() is { Length: 2 } arguments) - { - Type requestType = arguments[0]; - Type responseType = arguments[1]; + Type requestType = handlerArguments[0]; + Type responseType = handlerArguments[1]; - Type wrapperType = typeof(HandlerWrapper<,>).MakeGenericType(requestType, responseType); + Type wrapperType = typeof(HandlerWrapper<,>).MakeGenericType(requestType, responseType); - services.TryAdd(new ServiceDescriptor(typeof(THandler), typeof(THandler), lifetime)); - services.Add(new ServiceDescriptor(wrapperType, - provider => provider.GetService()?.Create(wrapperType, + services.TryAdd(new ServiceDescriptor(typeof(THandler), typeof(THandler), lifetime)); + services.Add(new ServiceDescriptor( + wrapperType, + provider => + provider.GetService()?.Create( + wrapperType, provider.GetRequiredService(), - provider.GetServices(typeof(IPipelineBehavior<,>).MakeGenericType(requestType, responseType)))!, - lifetime - )); - } + provider.GetServices(typeof(IPipelineBehavior<,>).MakeGenericType(requestType, responseType)) + )!, + lifetime + )); } } + return services; } return services; } + public static IServiceCollection AddNotificationRelay(this IServiceCollection services) where TFromNotification : diff --git a/Hyperbar/Lifecycles/INavigationDescriptor.cs b/Hyperbar/Lifecycles/INavigationDescriptor.cs new file mode 100644 index 0000000..72c7013 --- /dev/null +++ b/Hyperbar/Lifecycles/INavigationDescriptor.cs @@ -0,0 +1,7 @@ +namespace Hyperbar; + +public interface INavigationDescriptor +{ + Type Type { get; set; } +} + diff --git a/Hyperbar/Lifecycles/INavigationHandler.cs b/Hyperbar/Lifecycles/INavigationHandler.cs new file mode 100644 index 0000000..dcae92d --- /dev/null +++ b/Hyperbar/Lifecycles/INavigationHandler.cs @@ -0,0 +1,7 @@ +namespace Hyperbar; + +public interface INavigationHandler; + +public interface INavigationHandler : + INotificationHandler>, + INavigationHandler; \ No newline at end of file diff --git a/Hyperbar/Lifecycles/Navigate.cs b/Hyperbar/Lifecycles/Navigate.cs new file mode 100644 index 0000000..fb8143b --- /dev/null +++ b/Hyperbar/Lifecycles/Navigate.cs @@ -0,0 +1,8 @@ + +namespace Hyperbar; + +public record Navigate(object Key) : + INotification; + +public record Navigate(TTemplate Template, object Content) : + INotification; diff --git a/Hyperbar/Lifecycles/NavigateHandler.cs b/Hyperbar/Lifecycles/NavigateHandler.cs new file mode 100644 index 0000000..dfc0c9e --- /dev/null +++ b/Hyperbar/Lifecycles/NavigateHandler.cs @@ -0,0 +1,53 @@ +using Microsoft.Extensions.DependencyInjection; + +namespace Hyperbar; + +public class NavigateHandler : + INotificationHandler +{ + private readonly IEnumerable contentTemplateDescriptors; + private readonly IServiceProvider provider; + private readonly IMediator mediator; + private readonly IEnumerable navigationDescriptors; + + public NavigateHandler(IServiceProvider provider, + IMediator mediator, + IEnumerable navigationDescriptors, + IEnumerable contentTemplateDescriptors) + { + this.provider = provider; + this.mediator = mediator; + this.navigationDescriptors = navigationDescriptors; + this.contentTemplateDescriptors = contentTemplateDescriptors; + } + + public async Task Handle(Navigate args, + CancellationToken cancellationToken) + { + if (contentTemplateDescriptors.FirstOrDefault(x => x.Key == args.Key) + is IContentTemplateDescriptor contentTemplateDescriptor) + { + if (navigationDescriptors.FirstOrDefault(x => contentTemplateDescriptor.TemplateType == x.Type || + contentTemplateDescriptor.TemplateType.BaseType == x.Type) is { } navigationDescriptor) + { + if (contentTemplateDescriptor.TemplateType == navigationDescriptor.Type || + contentTemplateDescriptor.TemplateType.BaseType == navigationDescriptor.Type) + { + if (provider.GetRequiredKeyedService(contentTemplateDescriptor.TemplateType, + contentTemplateDescriptor.Key) is { } template) + { + Type navigateType = typeof(Navigate<>) + .MakeGenericType(navigationDescriptor.Type); + + if (Activator.CreateInstance(navigateType, + new object[] { template, args.Key }) is object navigate) + { + await mediator.PublishAsync(navigate, cancellationToken); + } + } + } + } + } + } +} + diff --git a/Hyperbar/Lifecycles/NavigationDescriptor.cs b/Hyperbar/Lifecycles/NavigationDescriptor.cs new file mode 100644 index 0000000..9da26de --- /dev/null +++ b/Hyperbar/Lifecycles/NavigationDescriptor.cs @@ -0,0 +1,8 @@ +namespace Hyperbar; + +public record NavigationDescriptor : + INavigationDescriptor +{ + public required Type Type { get; set; } +} + diff --git a/Hyperbar/Lifecycles/Remove.cs b/Hyperbar/Lifecycles/Remove.cs index 4b5d06b..ecb06c0 100644 --- a/Hyperbar/Lifecycles/Remove.cs +++ b/Hyperbar/Lifecycles/Remove.cs @@ -1,36 +1,5 @@  namespace Hyperbar; -public record Remove(TValue Value) : INotification; - -public record Navigate(object Key) : - INotification; - -public class NavigateHandler : - INotificationHandler -{ - private readonly IEnumerable descriptors; - - public NavigateHandler(IEnumerable descriptors, - IServiceProvider provider) - { - this.descriptors = descriptors; - } - - public Task Handle(Navigate args, - CancellationToken cancellationToken) - { - if (descriptors.FirstOrDefault(x => x.Key == args.Key) - is IContentTemplateDescriptor descriptor) - { - //if (provider.GetRequiredKeyedService(descriptor.TemplateType, - // descriptor.Key) is { } template) - //{ - // return template; - //} - } - - throw new NotImplementedException(); - } -} - +public record Remove(TValue Value) : + INotification; \ No newline at end of file diff --git a/Hyperbar/Mediators/IMediator.cs b/Hyperbar/Mediators/IMediator.cs index 9cf4034..a800ac0 100644 --- a/Hyperbar/Mediators/IMediator.cs +++ b/Hyperbar/Mediators/IMediator.cs @@ -19,12 +19,13 @@ public interface IMediator where TNotification : INotification; - Task PublishAsync(TNotification notification, + Task PublishAsync(object notification, + CancellationToken cancellationToken = default); + + Task PublishAsync(object notification, Func, Task> marshal, object? key = null, - CancellationToken cancellationToken = default) - where TNotification : - INotification; + CancellationToken cancellationToken = default); Task PublishAsync(CancellationToken cancellationToken = default) where TNotification : diff --git a/Hyperbar/Mediators/INotificationHandler.cs b/Hyperbar/Mediators/INotificationHandler.cs index 5a745ec..e105e34 100644 --- a/Hyperbar/Mediators/INotificationHandler.cs +++ b/Hyperbar/Mediators/INotificationHandler.cs @@ -6,5 +6,5 @@ public interface INotificationHandler : INotification { Task Handle(TNotification args, - CancellationToken cancellationToken); + CancellationToken cancellationToken = default); } \ No newline at end of file diff --git a/Hyperbar/Mediators/Mediator.cs b/Hyperbar/Mediators/Mediator.cs index 285be2c..0b2a812 100644 --- a/Hyperbar/Mediators/Mediator.cs +++ b/Hyperbar/Mediators/Mediator.cs @@ -8,7 +8,7 @@ public class Mediator(IServiceProvider provider, IDispatcher dispatcher) : IMediator { - private readonly ConcurrentDictionary> subscriptions = []; + private readonly ConcurrentDictionary> handlers = []; public Task PublishAsync(object key, CancellationToken cancellationToken = default) @@ -36,30 +36,38 @@ public class Mediator(IServiceProvider provider, key, cancellationToken); } - public Task PublishAsync(TNotification notification, + public Task PublishAsync(object notification, Func, Task> marshal, object? key = null, CancellationToken cancellationToken = default) - where TNotification : - INotification { - List> handlers = - provider.GetServices>().ToList(); + Type notificationType = notification.GetType(); - foreach (KeyValuePair> subscriber in subscriptions) + List handlers = provider.GetServices(typeof(INotificationHandler<>) + .MakeGenericType(notificationType)).ToList(); + + foreach (KeyValuePair> subscriber in this.handlers) { - if (subscriber.Key.Equals($"{(key is not null ? $"{key}:" : "")}{typeof(TNotification)}")) + if (subscriber.Key.Equals($"{key?.ToString()}:{notificationType}")) { - foreach (dynamic handler in subscriber.Value) - { - handlers.Add(handler); - } + handlers.AddRange(subscriber.Value); } } - foreach (INotificationHandler handler in handlers) + foreach (object? handler in handlers) { - marshal(() => handler.Handle(notification, cancellationToken)); + if (handler is not null) + { + Type? handlerType = handler.GetType(); + MethodInfo? handleMethod = handlerType.GetMethod("Handle", + [notificationType, typeof(CancellationToken)]); + + if (handleMethod is not null) + { + marshal(() => (Task)handleMethod.Invoke(handler, new object[] { notification, + cancellationToken })!); + } + } } return Task.CompletedTask; @@ -71,6 +79,13 @@ public class Mediator(IServiceProvider provider, new() => PublishAsync(new TNotification(), args => dispatcher.InvokeAsync(async () => await args()), null, cancellationToken); + public Task PublishAsync(object notification, + CancellationToken cancellationToken = default) + { + return PublishAsync(notification, args => dispatcher.InvokeAsync(async () => await args()), + null, cancellationToken); + } + public Task SendAsync(IRequest request, CancellationToken cancellationToken = default) { @@ -116,7 +131,7 @@ public class Mediator(IServiceProvider provider, { if (interfaceType.GetGenericArguments().FirstOrDefault() is Type argumentType) { - subscriptions.AddOrUpdate($"{(key is not null ? $"{key}:" : "")}{argumentType}", new List { handler }, (value, collection) => + handlers.AddOrUpdate($"{(key is not null ? $"{key}:" : "")}{argumentType}", new List { handler }, (value, collection) => { collection.Add(handler); return collection;