From e57748934400d1579562fbe57a8e98c59fd4716e Mon Sep 17 00:00:00 2001 From: Alex Macocian Date: Thu, 22 Aug 2024 14:20:20 +0200 Subject: [PATCH] Implement context Implement navigation Implement options --- .../Attributes/OptionSectionAttribute.cs | 7 ++ .../Context/IMauiAppContextAccessor.cs | 6 ++ Net.Maui.Extensions/Context/MauiAppContext.cs | 28 +++++++ .../Context/MauiAppContextManager.cs | 6 ++ .../ControlFlow/INavigationService.cs | 16 ++++ .../ControlFlow/NavigationService.cs | 54 +++++++++++++ .../ControlFlow/ScopedApplicationShell.cs | 35 +++++++++ Net.Maui.Extensions/ExtendedApplication.cs | 11 +++ .../MauiAppBuilderExtensions.cs | 76 +++++++++++++++++++ .../Net.Maui.Extensions.csproj | 9 ++- 10 files changed, 246 insertions(+), 2 deletions(-) create mode 100644 Net.Maui.Extensions/Attributes/OptionSectionAttribute.cs create mode 100644 Net.Maui.Extensions/Context/IMauiAppContextAccessor.cs create mode 100644 Net.Maui.Extensions/Context/MauiAppContext.cs create mode 100644 Net.Maui.Extensions/Context/MauiAppContextManager.cs create mode 100644 Net.Maui.Extensions/ControlFlow/INavigationService.cs create mode 100644 Net.Maui.Extensions/ControlFlow/NavigationService.cs create mode 100644 Net.Maui.Extensions/ControlFlow/ScopedApplicationShell.cs create mode 100644 Net.Maui.Extensions/ExtendedApplication.cs create mode 100644 Net.Maui.Extensions/MauiAppBuilderExtensions.cs diff --git a/Net.Maui.Extensions/Attributes/OptionSectionAttribute.cs b/Net.Maui.Extensions/Attributes/OptionSectionAttribute.cs new file mode 100644 index 0000000..d94d8d8 --- /dev/null +++ b/Net.Maui.Extensions/Attributes/OptionSectionAttribute.cs @@ -0,0 +1,7 @@ +namespace Net.Maui.Extensions.Attributes; + +[AttributeUsage(AttributeTargets.Class)] +public sealed class OptionSectionAttribute : Attribute +{ + public string? SectionName { get; set; } +} diff --git a/Net.Maui.Extensions/Context/IMauiAppContextAccessor.cs b/Net.Maui.Extensions/Context/IMauiAppContextAccessor.cs new file mode 100644 index 0000000..abdbe8e --- /dev/null +++ b/Net.Maui.Extensions/Context/IMauiAppContextAccessor.cs @@ -0,0 +1,6 @@ +namespace Net.Maui.Extensions.Context; + +public interface IMauiAppContextAccessor +{ + public MauiAppContext Context { get; } +} diff --git a/Net.Maui.Extensions/Context/MauiAppContext.cs b/Net.Maui.Extensions/Context/MauiAppContext.cs new file mode 100644 index 0000000..abf6bf3 --- /dev/null +++ b/Net.Maui.Extensions/Context/MauiAppContext.cs @@ -0,0 +1,28 @@ +namespace Net.Maui.Extensions.Context; + +public sealed class MauiAppContext +{ + private readonly Dictionary resources = []; + + internal MauiAppContext() + { + } + + public void SetContext(string key, T value) + { + this.resources.Add(key, value); + } + + public bool TryGetContext(string key, out T? value) + { + if (this.resources.TryGetValue(key, out var valueObj) && + valueObj is T typedValue) + { + value = typedValue; + return true; + } + + value = default; + return false; + } +} diff --git a/Net.Maui.Extensions/Context/MauiAppContextManager.cs b/Net.Maui.Extensions/Context/MauiAppContextManager.cs new file mode 100644 index 0000000..c043578 --- /dev/null +++ b/Net.Maui.Extensions/Context/MauiAppContextManager.cs @@ -0,0 +1,6 @@ +namespace Net.Maui.Extensions.Context; + +internal sealed class MauiAppContextManager : IMauiAppContextAccessor +{ + public MauiAppContext Context { get; } = new(); +} diff --git a/Net.Maui.Extensions/ControlFlow/INavigationService.cs b/Net.Maui.Extensions/ControlFlow/INavigationService.cs new file mode 100644 index 0000000..1ff36ad --- /dev/null +++ b/Net.Maui.Extensions/ControlFlow/INavigationService.cs @@ -0,0 +1,16 @@ +namespace Net.Maui.Extensions.ControlFlow; + +public interface INavigationService +{ + Task GoTo(bool animated = false) + where T : ContentPage; + + Task GoToModal(bool animated = false) + where T : ContentPage; + + Task GoBack(bool animated = false); + + Task GoBackModal(bool animated = false); + + Task GoBackToRoot(bool animated = false); +} diff --git a/Net.Maui.Extensions/ControlFlow/NavigationService.cs b/Net.Maui.Extensions/ControlFlow/NavigationService.cs new file mode 100644 index 0000000..73d5085 --- /dev/null +++ b/Net.Maui.Extensions/ControlFlow/NavigationService.cs @@ -0,0 +1,54 @@ +using Microsoft.Extensions.Logging; +using System.Core.Extensions; +using System.Extensions.Core; + +namespace Net.Maui.Extensions.ControlFlow; + +internal sealed class NavigationService : INavigationService +{ + private readonly ScopedApplicationShell scopedApplicationShell; + private readonly ILogger logger; + + public NavigationService( + ScopedApplicationShell scopedApplicationShell, + ILogger logger) + { + this.scopedApplicationShell = scopedApplicationShell.ThrowIfNull(); + this.logger = logger.ThrowIfNull(); + } + + public async Task GoBack(bool animated = false) + { + var scopedLogger = this.logger.CreateScopedLogger(); + scopedLogger.LogInformation("Going back"); + await this.scopedApplicationShell.Navigation.PopAsync(animated); + } + + public async Task GoBackModal(bool animated = false) + { + var scopedLogger = this.logger.CreateScopedLogger(); + scopedLogger.LogInformation("Going back"); + await this.scopedApplicationShell.Navigation.PopModalAsync(animated); + } + + public async Task GoBackToRoot(bool animated = false) + { + var scopedLogger = this.logger.CreateScopedLogger(); + scopedLogger.LogInformation("Going back to root"); + await this.scopedApplicationShell.Navigation.PopToRootAsync(animated); + } + + public async Task GoTo(bool animated = false) where T : ContentPage + { + var scopedLogger = this.logger.CreateScopedLogger(); + scopedLogger.LogInformation($"Going to {typeof(T).Name}"); + await this.scopedApplicationShell.PushScoped(); + } + + public async Task GoToModal(bool animated = false) where T : ContentPage + { + var scopedLogger = this.logger.CreateScopedLogger(); + scopedLogger.LogInformation($"Going to {typeof(T).Name}"); + await this.scopedApplicationShell.PushModalScoped(); + } +} diff --git a/Net.Maui.Extensions/ControlFlow/ScopedApplicationShell.cs b/Net.Maui.Extensions/ControlFlow/ScopedApplicationShell.cs new file mode 100644 index 0000000..a8d1c6a --- /dev/null +++ b/Net.Maui.Extensions/ControlFlow/ScopedApplicationShell.cs @@ -0,0 +1,35 @@ +using System.Core.Extensions; + +namespace Net.Maui.Extensions.ControlFlow; + +internal class ScopedApplicationShell : Shell +{ + private readonly IServiceProvider serviceProvider; + + public ScopedApplicationShell( + IServiceProvider serviceProvider) + { + this.serviceProvider = serviceProvider.ThrowIfNull(); + } + + public async Task PushScoped() + where T : ContentPage + { + var page = this.GetScopedPage(); + await this.Navigation.PushAsync(page); + } + + public async Task PushModalScoped() + where T : ContentPage + { + var page = this.GetScopedPage(); + await this.Navigation.PushModalAsync(page); + } + + private T GetScopedPage() + where T : ContentPage + { + using var scope = this.serviceProvider.CreateScope(); + return scope.ServiceProvider.GetRequiredService(); + } +} diff --git a/Net.Maui.Extensions/ExtendedApplication.cs b/Net.Maui.Extensions/ExtendedApplication.cs new file mode 100644 index 0000000..17f8ac3 --- /dev/null +++ b/Net.Maui.Extensions/ExtendedApplication.cs @@ -0,0 +1,11 @@ +using System.Core.Extensions; + +namespace Net.Maui.Extensions; + +public abstract class ExtendedApplication : Application +{ + public ExtendedApplication(Shell shell) + { + this.MainPage = shell.ThrowIfNull(); + } +} diff --git a/Net.Maui.Extensions/MauiAppBuilderExtensions.cs b/Net.Maui.Extensions/MauiAppBuilderExtensions.cs new file mode 100644 index 0000000..346a784 --- /dev/null +++ b/Net.Maui.Extensions/MauiAppBuilderExtensions.cs @@ -0,0 +1,76 @@ +using Microsoft.Extensions.Configuration; +using Net.Maui.Extensions.Attributes; +using Net.Maui.Extensions.Context; +using Net.Maui.Extensions.ControlFlow; +using System.Core.Extensions; +using System.Reflection; + +namespace Net.Maui.Extensions; + +public static class MauiAppBuilderExtensions +{ + private static readonly Lazy ConfigureMethod = new(() => + { + return typeof(OptionsConfigurationServiceCollectionExtensions)? + .GetMethods(BindingFlags.Static | BindingFlags.Public) + .FirstOrDefault(m => m.Name == nameof(OptionsConfigurationServiceCollectionExtensions.Configure) + && m.GetGenericArguments().Length == 1 + && m.GetParameters().Length == 2 + && m.GetParameters()[0].ParameterType == typeof(IServiceCollection) + && m.GetParameters()[1].ParameterType == typeof(IConfiguration)) ?? throw new InvalidOperationException($"Unable to resolve {nameof(OptionsConfigurationServiceCollectionExtensions.Configure)} method"); + }, true); + + public static MauiAppBuilder WithExtendedOptions(this MauiAppBuilder appBuilder) + { + appBuilder.ThrowIfNull(); + foreach ((var optionType, var optionAttribute) in Assembly.GetCallingAssembly()?.GetTypes()? + .Select(t => (t, t.GetCustomAttributes(true).OfType().FirstOrDefault())) + .Where(t => t.Item2 is not null) ?? []) + { + var section = appBuilder.Configuration.GetRequiredSection(optionAttribute?.SectionName ?? optionType.Name); + // Use reflection to call the generic Configure method + var method = ConfigureMethod.Value + .MakeGenericMethod(optionType); + + method?.Invoke(null, [appBuilder.Services, section]); + } + + return appBuilder; + } + + public static MauiAppBuilder WithMauiAppContext(this MauiAppBuilder appBuilder) + { + appBuilder.ThrowIfNull() + .Services.AddScoped(); + + return appBuilder; + } + + public static MauiAppBuilder WithExtendedApp(this MauiAppBuilder appBuilder) + where T : ExtendedApplication + { + appBuilder.ThrowIfNull().UseMauiApp(); + return appBuilder; + } + + public static MauiAppBuilder WithNavigation(this MauiAppBuilder appBuilder) + { + appBuilder.ThrowIfNull(); + appBuilder.Services.AddScoped(); + appBuilder.Services.AddScoped(); + + return appBuilder; + } + + public static MauiAppBuilder WithPages(this MauiAppBuilder appBuilder) + { + appBuilder.ThrowIfNull(); + foreach (var pageType in Assembly.GetCallingAssembly()?.GetTypes()?.Where(t => t.IsAssignableTo(typeof(ContentPage))) ?? []) + { + appBuilder.Services.AddScoped(pageType); + appBuilder.Services.AddScoped(typeof(ContentPage), sp => sp.GetRequiredService(pageType)); + } + + return appBuilder; + } +} diff --git a/Net.Maui.Extensions/Net.Maui.Extensions.csproj b/Net.Maui.Extensions/Net.Maui.Extensions.csproj index 549d4db..be6fe9b 100644 --- a/Net.Maui.Extensions/Net.Maui.Extensions.csproj +++ b/Net.Maui.Extensions/Net.Maui.Extensions.csproj @@ -45,9 +45,14 @@ - - + + + + + + +