namespace BotPages.Core { /// /// Реализация сервиса навигации страниц. /// public sealed class NavigationService : INavigationService { private readonly IPageRegistry _pages; private readonly IStateStore _store; /// /// Создаёт сервис навигации. /// public NavigationService(IPageRegistry pages, IStateStore store) { _pages = pages; _store = store; } /// /// Выполняет push новой страницы и вызывает её Enter. /// public async Task PushAsync(string pageId, object? args, UpdateContext ctx, CancellationToken ct) { var state = await _store.GetAsync(ctx.User.Id, ct); state.Stack.Add(new NavEntry(pageId, args)); await _store.SaveAsync(state, ct); var pr = await _pages.Get(pageId).EnterAsync(ctx, ct); await ApplyResultAsync(ctx, pr, ct); } /// /// Выполняет replace текущей страницы и вызывает Enter новой. /// public async Task ReplaceAsync(string pageId, object? args, UpdateContext ctx, CancellationToken ct) { var state = await _store.GetAsync(ctx.User.Id, ct); if (state.Stack.Count > 0) state.Stack[^1] = new NavEntry(pageId, args); else state.Stack.Add(new NavEntry(pageId, args)); await _store.SaveAsync(state, ct); var pr = await _pages.Get(pageId).EnterAsync(ctx, ct); await ApplyResultAsync(ctx, pr, ct); } /// /// Возвращается назад по стеку и вызывает Enter предыдущей. /// public async Task PopAsync(UpdateContext ctx, CancellationToken ct) { var state = await _store.GetAsync(ctx.User.Id, ct); if (state.Stack.Count == 0) return; var currentId = state.Stack[^1].PageId; await _pages.Get(currentId).ExitAsync(ctx, ct); state.Stack.RemoveAt(state.Stack.Count - 1); await _store.SaveAsync(state, ct); var next = state.Stack.Count > 0 ? state.Stack[^1].PageId : null; if (next is not null) { var pr = await _pages.Get(next).EnterAsync(ctx, ct); await ApplyResultAsync(ctx, pr, ct); } } /// /// Применяет декларативный результат страницы (навигация, текст, файлы). /// public async Task ApplyResultAsync(UpdateContext ctx, PageResult result, CancellationToken ct) { if (result.NavigateTo is not null) { if (result.NavigateTo.Replace) await ReplaceAsync(result.NavigateTo.PageId, result.NavigateTo.Args, ctx, ct); else await PushAsync(result.NavigateTo.PageId, result.NavigateTo.Args, ctx, ct); return; // навигация сама вызовет Enter новой страницы и применит её результат } if (result.Message is not null) await ctx.Client.SendTextAsync(ctx.Chat.Id, result.Message, result.Actions, ct); if (result.Files is not null) await ctx.Client.SendFilesAsync(ctx.Chat.Id, result.Files, ct); } /// /// Возвращает текущую запись стека. /// public async Task CurrentAsync(UpdateContext ctx, CancellationToken ct) { var state = await _store.GetAsync(ctx.User.Id, ct); return state.Stack.Count == 0 ? null : state.Stack[^1]; } /// /// Возвращает весь стек навигации. /// public async Task> StackAsync(UpdateContext ctx, CancellationToken ct) { var state = await _store.GetAsync(ctx.User.Id, ct); return state.Stack.AsReadOnly(); } } }