116 lines
3.9 KiB
C#
116 lines
3.9 KiB
C#
using BotPages.Core.Abstractions;
|
||
using BotPages.Core.Routing;
|
||
using System.Collections.Concurrent;
|
||
|
||
namespace BotPages.Core;
|
||
|
||
/// <summary>
|
||
/// Сервис навигации между страницами.
|
||
/// Позволяет выполнять переходы, замену страниц и передачу аргументов.
|
||
/// </summary>
|
||
public sealed class NavigationService
|
||
{
|
||
private readonly RoutesRegistry _routes;
|
||
private readonly Dictionary<Type, Page> _singletonPages = new();
|
||
private readonly ConcurrentDictionary<CompositeSessionKey, Page> _sessionPages = new();
|
||
private Type? _defaultPage;
|
||
|
||
/// <summary>
|
||
/// Создать сервис навигации.
|
||
/// </summary>
|
||
internal NavigationService(RoutesRegistry routes)
|
||
{
|
||
_routes = routes;
|
||
}
|
||
|
||
internal void AddDefaultPage<TPage>() where TPage : SingletonPage
|
||
{
|
||
_defaultPage = typeof(TPage);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Перейти по маршруту без аргументов.
|
||
/// </summary>
|
||
public Task GoToHome(PageContext ctx, CancellationToken ct)
|
||
{
|
||
return NavigateAsync(_defaultPage!, ctx, null, ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Перейти по маршруту без аргументов.
|
||
/// </summary>
|
||
public Task GoToAsync(string route, PageContext ctx, CancellationToken ct)
|
||
{
|
||
var pageType = _routes.Resolve(route);
|
||
return NavigateAsync(pageType!, ctx, null, ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Перейти по маршруту с аргументами.
|
||
/// </summary>
|
||
public Task GoToAsync<TArgs>(string route, TArgs args, PageContext ctx, CancellationToken ct)
|
||
{
|
||
var pageType = _routes.Resolve(route);
|
||
return NavigateAsync(pageType!, ctx, args!, ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Перейти на страницу без аргументов.
|
||
/// </summary>
|
||
public Task GoToAsync<TPage>(PageContext ctx, CancellationToken ct) where TPage : Page
|
||
=> NavigateAsync(typeof(TPage), ctx, null, ct);
|
||
|
||
/// <summary>
|
||
/// Перейти на страницу с аргументами.
|
||
/// </summary>
|
||
public Task GoToAsync<TPage, TArgs>(PageContext ctx, TArgs args, CancellationToken ct) where TPage : StatefullPage<TArgs>
|
||
=> NavigateAsync(typeof(TPage), ctx, args!, ct);
|
||
|
||
/// <summary>
|
||
/// Заменить текущую страницу.
|
||
/// </summary>
|
||
public Task ReplaceWithAsync<TPage>(PageContext ctx, CancellationToken ct) where TPage : Page
|
||
=> NavigateAsync(typeof(TPage), ctx, null, ct, replace: true);
|
||
|
||
internal async Task NavigateAsync(Type pageType, PageContext ctx, object? args, CancellationToken ct, bool replace = false)
|
||
{
|
||
Page? page;
|
||
|
||
if (typeof(SingletonPage).IsAssignableFrom(pageType))
|
||
{
|
||
// Singleton: один объект на всё приложение
|
||
if (!_singletonPages.TryGetValue(pageType, out page))
|
||
{
|
||
page = (Page)Activator.CreateInstance(pageType)!;
|
||
_singletonPages[pageType] = page;
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// Stateful: новый объект на пользователя
|
||
page = (Page)Activator.CreateInstance(pageType)!;
|
||
}
|
||
|
||
if (_sessionPages.TryGetValue(ctx.SessionKey, out var currentPage))
|
||
{
|
||
if (currentPage.GetType() != pageType || replace)
|
||
{
|
||
await currentPage.OnLeave(ctx, ct);
|
||
}
|
||
}
|
||
|
||
_sessionPages[ctx.SessionKey] = page;
|
||
|
||
if (args is null)
|
||
await page.OnEnter(ctx, ct);
|
||
else
|
||
await (page as dynamic).OnEnter(ctx, (dynamic)args, ct);
|
||
}
|
||
|
||
/// <summary>
|
||
/// Восстановить текущую страницу из StateStorage.
|
||
/// </summary>
|
||
public Page? ResolveCurrentPage(PageContext ctx)
|
||
=> _sessionPages.TryGetValue(ctx.SessionKey, out var page) ? page : null;
|
||
}
|