mirror of
https://github.com/AlexMacocian/Net.Maui.Extensions.git
synced 2026-07-15 14:59:31 +00:00
Fix navigation
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
namespace Net.Maui.Extensions.ControlFlow;
|
||||
|
||||
public interface IMainPagePresenter<TMainPage>
|
||||
where TMainPage : ContentPage
|
||||
{
|
||||
}
|
||||
@@ -2,15 +2,10 @@
|
||||
|
||||
public interface INavigationService
|
||||
{
|
||||
Task GoTo<T>(bool animated = false)
|
||||
void GoTo<T>(bool animated = false)
|
||||
where T : ContentPage;
|
||||
|
||||
Task GoToModal<T>(bool animated = false)
|
||||
where T : ContentPage;
|
||||
void GoBack(bool animated = false);
|
||||
|
||||
Task GoBack(bool animated = false);
|
||||
|
||||
Task GoBackModal(bool animated = false);
|
||||
|
||||
Task GoBackToRoot(bool animated = false);
|
||||
void GoBackToRoot(bool animated = false);
|
||||
}
|
||||
|
||||
@@ -1,54 +1,106 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
using System.Extensions.Core;
|
||||
|
||||
namespace Net.Maui.Extensions.ControlFlow;
|
||||
|
||||
internal sealed class NavigationService : INavigationService
|
||||
{
|
||||
private readonly ScopedApplicationShell scopedApplicationShell;
|
||||
private readonly Stack<ScopedPageContext> modalStack = new();
|
||||
private readonly Stack<ScopedPageContext> navigationStack = new();
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly ILogger<NavigationService> logger;
|
||||
|
||||
private ExtendedApplication? extendedApplication;
|
||||
|
||||
public NavigationService(
|
||||
ScopedApplicationShell scopedApplicationShell,
|
||||
IServiceProvider serviceProvider,
|
||||
ILogger<NavigationService> logger)
|
||||
{
|
||||
this.scopedApplicationShell = scopedApplicationShell.ThrowIfNull();
|
||||
this.serviceProvider = serviceProvider.ThrowIfNull();
|
||||
this.logger = logger.ThrowIfNull();
|
||||
}
|
||||
|
||||
public async Task GoBack(bool animated = false)
|
||||
public void GoBack(bool animated = false)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
scopedLogger.LogInformation("Going back");
|
||||
await this.scopedApplicationShell.Navigation.PopAsync(animated);
|
||||
if (this.navigationStack.Count <= 1)
|
||||
{
|
||||
scopedLogger.LogError("Cannot go back. Navigation stack is at root");
|
||||
return;
|
||||
}
|
||||
|
||||
scopedLogger.LogDebug("Going back");
|
||||
if (this.navigationStack.TryPop(out var context))
|
||||
{
|
||||
context.Scope?.Dispose();
|
||||
}
|
||||
|
||||
this.ShowCurrentPage();
|
||||
}
|
||||
|
||||
public async Task GoBackModal(bool animated = false)
|
||||
public void GoBackToRoot(bool animated = false)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
scopedLogger.LogInformation("Going back");
|
||||
await this.scopedApplicationShell.Navigation.PopModalAsync(animated);
|
||||
scopedLogger.LogDebug("Going back to root");
|
||||
while (this.navigationStack.Count > 1 && this.navigationStack.TryPop(out var context))
|
||||
{
|
||||
context.Scope?.Dispose();
|
||||
}
|
||||
|
||||
this.ShowCurrentPage();
|
||||
}
|
||||
|
||||
public async Task GoBackToRoot(bool animated = false)
|
||||
public void GoTo<T>(bool animated = false) where T : ContentPage
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
scopedLogger.LogInformation("Going back to root");
|
||||
await this.scopedApplicationShell.Navigation.PopToRootAsync(animated);
|
||||
scopedLogger.LogDebug($"Going to {typeof(T).Name}");
|
||||
|
||||
var context = this.GetScopedPageAndProvider<T>();
|
||||
this.navigationStack.Push(context);
|
||||
this.ShowCurrentPage();
|
||||
}
|
||||
|
||||
public async Task GoTo<T>(bool animated = false) where T : ContentPage
|
||||
public void SetNavigationRoot(ExtendedApplication extendedApplication, Type rootPageType)
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
scopedLogger.LogInformation($"Going to {typeof(T).Name}");
|
||||
await this.scopedApplicationShell.PushScoped<T>();
|
||||
scopedLogger.LogDebug($"Setting navigation root {rootPageType.Name}");
|
||||
|
||||
this.extendedApplication = extendedApplication.ThrowIfNull();
|
||||
var context = this.GetScopedPageAndProvider(rootPageType);
|
||||
this.navigationStack.Push(context);
|
||||
this.ShowCurrentPage();
|
||||
}
|
||||
|
||||
public async Task GoToModal<T>(bool animated = false) where T : ContentPage
|
||||
private void ShowCurrentPage()
|
||||
{
|
||||
var scopedLogger = this.logger.CreateScopedLogger();
|
||||
scopedLogger.LogInformation($"Going to {typeof(T).Name}");
|
||||
await this.scopedApplicationShell.PushModalScoped<T>();
|
||||
if (this.extendedApplication is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.navigationStack.TryPeek(out var currentContext))
|
||||
{
|
||||
this.extendedApplication.MainPage = currentContext.Page;
|
||||
}
|
||||
}
|
||||
|
||||
private ScopedPageContext GetScopedPageAndProvider<T>()
|
||||
where T : ContentPage
|
||||
{
|
||||
var scope = this.serviceProvider.CreateScope();
|
||||
return new ScopedPageContext { Page = scope.ServiceProvider.GetRequiredService<T>(), Scope = scope };
|
||||
}
|
||||
|
||||
private ScopedPageContext GetScopedPageAndProvider(Type type)
|
||||
{
|
||||
if (!type.IsAssignableTo(typeof(ContentPage)))
|
||||
{
|
||||
throw new InvalidOperationException($"{type.Name} must be of type {nameof(ContentPage)}");
|
||||
}
|
||||
|
||||
var scope = this.serviceProvider.CreateScope();
|
||||
return new ScopedPageContext { Page = scope.ServiceProvider.GetRequiredService(type).Cast<ContentPage>(), Scope = scope };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
using System.Core.Extensions;
|
||||
|
||||
namespace Net.Maui.Extensions.ControlFlow;
|
||||
|
||||
public class ScopedApplicationShell : Shell
|
||||
{
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
internal 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,7 @@
|
||||
namespace Net.Maui.Extensions.ControlFlow;
|
||||
|
||||
internal sealed class ScopedPageContext
|
||||
{
|
||||
public ContentPage? Page { get; init; }
|
||||
public IServiceScope? Scope { get; init; }
|
||||
}
|
||||
@@ -1,20 +1,35 @@
|
||||
using Net.Maui.Extensions.ControlFlow;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
|
||||
namespace Net.Maui.Extensions;
|
||||
|
||||
public abstract class ExtendedApplication : Application
|
||||
{
|
||||
public ScopedApplicationShell ScopedShell { get; }
|
||||
private readonly INavigationService navigationService;
|
||||
|
||||
public ExtendedApplication(ScopedApplicationShell shell)
|
||||
public ExtendedApplication(
|
||||
INavigationService navigationService)
|
||||
{
|
||||
if (shell is not ScopedApplicationShell)
|
||||
this.navigationService = navigationService.ThrowIfNull();
|
||||
this.SetMainPage();
|
||||
}
|
||||
|
||||
private void SetMainPage()
|
||||
{
|
||||
var currentType = this.GetType();
|
||||
|
||||
// Find the interface that implements IMainPagePresenter<>
|
||||
var interfaceType = currentType
|
||||
.GetInterfaces()
|
||||
.FirstOrDefault(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IMainPagePresenter<>));
|
||||
|
||||
if (interfaceType is null)
|
||||
{
|
||||
throw new InvalidOperationException($"Shell is not of type {typeof(ScopedApplicationShell).Name}");
|
||||
throw new InvalidOperationException($"{currentType.Name} must implement IMainPagePresenter<TMainPage>");
|
||||
}
|
||||
|
||||
this.MainPage = shell.ThrowIfNull();
|
||||
this.ScopedShell = shell.ThrowIfNull();
|
||||
var mainPageType = interfaceType.GetGenericArguments()[0];
|
||||
this.navigationService.Cast<NavigationService>().SetNavigationRoot(this, mainPageType);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Net.Maui.Extensions.Attributes;
|
||||
using Net.Maui.Extensions.Context;
|
||||
using Net.Maui.Extensions.ControlFlow;
|
||||
using System.Core.Extensions;
|
||||
using System.Extensions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace Net.Maui.Extensions;
|
||||
@@ -46,19 +48,18 @@ public static class MauiAppBuilderExtensions
|
||||
return appBuilder;
|
||||
}
|
||||
|
||||
public static MauiAppBuilder WithExtendedApp<T>(this MauiAppBuilder appBuilder)
|
||||
where T : ExtendedApplication
|
||||
public static MauiAppBuilder WithExtendedApp<TApp, TMainPage>(this MauiAppBuilder appBuilder)
|
||||
where TApp : ExtendedApplication, IMainPagePresenter<TMainPage>
|
||||
where TMainPage : ContentPage
|
||||
{
|
||||
appBuilder.ThrowIfNull().UseMauiApp<T>();
|
||||
appBuilder.ThrowIfNull().UseMauiApp<TApp>();
|
||||
return appBuilder;
|
||||
}
|
||||
|
||||
public static MauiAppBuilder WithNavigation(this MauiAppBuilder appBuilder)
|
||||
{
|
||||
appBuilder.ThrowIfNull();
|
||||
appBuilder.Services.AddScoped<ScopedApplicationShell, ScopedApplicationShell>(sp => new ScopedApplicationShell(sp));
|
||||
appBuilder.Services.AddScoped<Shell, ScopedApplicationShell>(sp => sp.GetRequiredService<ScopedApplicationShell>());
|
||||
appBuilder.Services.AddScoped<INavigationService, NavigationService>();
|
||||
appBuilder.Services.AddSingleton<INavigationService, NavigationService>(sp => new NavigationService(sp, sp.GetRequiredService<ILogger<NavigationService>>()));
|
||||
|
||||
return appBuilder;
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Version>0.2.3</Version>
|
||||
<Version>0.2.4</Version>
|
||||
<Authors>Alexandru Macocian</Authors>
|
||||
<PackageLicenseFile>LICENSE</PackageLicenseFile>
|
||||
<PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
|
||||
@@ -52,7 +52,7 @@
|
||||
<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" />
|
||||
<PackageReference Include="SystemExtensions.NetCore" Version="1.6.6" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
Reference in New Issue
Block a user