Implement context

Implement navigation
Implement options
This commit is contained in:
2024-08-22 14:20:20 +02:00
parent 81962a23db
commit e577489344
10 changed files with 246 additions and 2 deletions
@@ -0,0 +1,7 @@
namespace Net.Maui.Extensions.Attributes;
[AttributeUsage(AttributeTargets.Class)]
public sealed class OptionSectionAttribute : Attribute
{
public string? SectionName { get; set; }
}
@@ -0,0 +1,6 @@
namespace Net.Maui.Extensions.Context;
public interface IMauiAppContextAccessor
{
public MauiAppContext Context { get; }
}
@@ -0,0 +1,28 @@
namespace Net.Maui.Extensions.Context;
public sealed class MauiAppContext
{
private readonly Dictionary<string, object?> resources = [];
internal MauiAppContext()
{
}
public void SetContext<T>(string key, T value)
{
this.resources.Add(key, value);
}
public bool TryGetContext<T>(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;
}
}
@@ -0,0 +1,6 @@
namespace Net.Maui.Extensions.Context;
internal sealed class MauiAppContextManager : IMauiAppContextAccessor
{
public MauiAppContext Context { get; } = new();
}
@@ -0,0 +1,16 @@
namespace Net.Maui.Extensions.ControlFlow;
public interface INavigationService
{
Task GoTo<T>(bool animated = false)
where T : ContentPage;
Task GoToModal<T>(bool animated = false)
where T : ContentPage;
Task GoBack(bool animated = false);
Task GoBackModal(bool animated = false);
Task GoBackToRoot(bool animated = false);
}
@@ -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<NavigationService> logger;
public NavigationService(
ScopedApplicationShell scopedApplicationShell,
ILogger<NavigationService> 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<T>(bool animated = false) where T : ContentPage
{
var scopedLogger = this.logger.CreateScopedLogger();
scopedLogger.LogInformation($"Going to {typeof(T).Name}");
await this.scopedApplicationShell.PushScoped<T>();
}
public async Task GoToModal<T>(bool animated = false) where T : ContentPage
{
var scopedLogger = this.logger.CreateScopedLogger();
scopedLogger.LogInformation($"Going to {typeof(T).Name}");
await this.scopedApplicationShell.PushModalScoped<T>();
}
}
@@ -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<T>()
where T : ContentPage
{
var page = this.GetScopedPage<T>();
await this.Navigation.PushAsync(page);
}
public async Task PushModalScoped<T>()
where T : ContentPage
{
var page = this.GetScopedPage<T>();
await this.Navigation.PushModalAsync(page);
}
private T GetScopedPage<T>()
where T : ContentPage
{
using var scope = this.serviceProvider.CreateScope();
return scope.ServiceProvider.GetRequiredService<T>();
}
}
@@ -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();
}
}
@@ -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<MethodInfo> 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<OptionSectionAttribute>().FirstOrDefault()))
.Where(t => t.Item2 is not null) ?? [])
{
var section = appBuilder.Configuration.GetRequiredSection(optionAttribute?.SectionName ?? optionType.Name);
// Use reflection to call the generic Configure<T> 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<IMauiAppContextAccessor, MauiAppContextManager>();
return appBuilder;
}
public static MauiAppBuilder WithExtendedApp<T>(this MauiAppBuilder appBuilder)
where T : ExtendedApplication
{
appBuilder.ThrowIfNull().UseMauiApp<T>();
return appBuilder;
}
public static MauiAppBuilder WithNavigation(this MauiAppBuilder appBuilder)
{
appBuilder.ThrowIfNull();
appBuilder.Services.AddScoped<Shell, ScopedApplicationShell>();
appBuilder.Services.AddScoped<INavigationService, NavigationService>();
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;
}
}
@@ -45,9 +45,14 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Maui.Controls" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="$(MauiVersion)" />
<PackageReference Include="Microsoft.CorrelationVector" Version="1.0.42" />
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="8.0.0" />
<PackageReference Include="Microsoft.Maui.Controls" Version="8.0.80" />
<PackageReference Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.80" />
<PackageReference Include="Microsoft.Extensions.Logging.Debug" Version="8.0.0" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />
<PackageReference Include="System.Text.RegularExpressions" Version="4.3.1" />
<PackageReference Include="SystemExtensions.NetCore" Version="1.6.5" />
</ItemGroup>
</Project>