Доработано Demo
All checks were successful
CI / build-test (push) Successful in 30s

This commit is contained in:
2025-12-05 13:33:25 +03:00
parent d817417a69
commit e6e5459280
10 changed files with 103 additions and 15 deletions

View File

@@ -34,7 +34,7 @@ public sealed class BotPagesApp
/// <summary>
/// Установить страницу по умолчанию.
/// </summary>
public BotPagesApp AddDefaultPage<TPage>() where TPage : Page
public BotPagesApp AddDefaultPage<TPage>() where TPage : SingletonPage
{
_navigation.AddDefaultPage<TPage>();
return this;

View File

@@ -22,6 +22,26 @@ public sealed class PageContext
/// <summary>Адаптер мессенджера.</summary>
public required IMessengerAdapter Adapter { get; init; }
//Storage
/// <summary>Получить состояние по ключу.</summary>
public Task<T?> GetStorageAsync<T>(string key, CancellationToken ct)
=> StateStorage.GetAsync<T>(SessionKey, key, ct);
/// <summary>Сохранить состояние по ключу.</summary>
public Task SetStorageAsync<T>(string key, T state, CancellationToken ct)
=> StateStorage.SetAsync<T>(SessionKey, key, state, ct);
/// <summary>Удалить состояние по ключу.</summary>
public Task<bool> RemoveStorageAsync(string key, CancellationToken ct)
=> StateStorage.RemoveAsync(SessionKey, key, ct);
/// <summary>Удалить все состояния по ключу.</summary>
public Task<bool> ClearStorageAsync(CancellationToken ct)
=> StateStorage.ClearAsync(SessionKey, ct);
//Adapter
/// <summary>
/// Отправить текстовое сообщение.
/// </summary>

View File

@@ -23,7 +23,7 @@ public sealed class NavigationService
_routes = routes;
}
internal void AddDefaultPage<TPage>() where TPage : Page
internal void AddDefaultPage<TPage>() where TPage : SingletonPage
{
_defaultPage = typeof(TPage);
}

View File

@@ -3,7 +3,7 @@
/// <summary>
/// Атрибут для свойств страницы, которые должны сохраняться в StateStorage.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class StatefullAttribute : Attribute
{
/// <summary>

View File

@@ -21,7 +21,7 @@ public abstract class StatefullPage : Page
/// <summary>
/// Загружает значения свойств из StateStorage.
/// </summary>
internal async Task LoadState(PageContext ctx, CancellationToken ct)
protected async Task LoadState(PageContext ctx, CancellationToken ct)
{
foreach (var prop in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{
@@ -40,7 +40,7 @@ public abstract class StatefullPage : Page
/// <summary>
/// Сохраняет значения свойств в StateStorage.
/// </summary>
internal async Task SaveState(PageContext ctx, CancellationToken ct)
protected async Task SaveState(PageContext ctx, CancellationToken ct)
{
foreach (var prop in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{

15
Demo/Models/Request.cs Normal file
View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Demo.Models
{
internal class Request
{
public string Title { get; set; }
public int FilesCount { get; set; }
public string Details { get; set; } = "";
}
}

View File

@@ -1,27 +1,57 @@
using BotPages.Core;
using BotPages.Core.Messaging;
using System.Xml;
namespace Demo.Pages;
/// <summary>
/// Страница ввода деталей заявки.
/// Страница с параметрами и сохранением состояния.
/// </summary>
public sealed class DetailsPage : StatefullPage<DetailsArgs>
{
[Statefull("Request")]
private Models.Request Request;
public override Task OnEnter(PageContext ctx, DetailsArgs args, CancellationToken ct)
=> new MessageBuilder(ctx)
{
Request = new()
{
Title = args.Title,
};
return new MessageBuilder(ctx)
.Text($"Заголовок: {args.Title}\nДобавьте детали или нажмите Далее.")
.Inline(new InlineButton("Далее", "next"), new InlineButton("Назад", "back"))
.Reply("Отмена")
.SendAsync(ct);
}
public override Task OnButton(PageContext ctx, string payload, CancellationToken ct)
=> payload switch
public override async Task OnButton(PageContext ctx, string payload, CancellationToken ct)
{
"next" => ctx.Navigation.GoToAsync<FilesPage>(ctx, ct),
"back" => ctx.Navigation.GoToAsync<TitlePage>(ctx, ct),
_ => Task.CompletedTask
switch (payload)
{
case "next":
{
await SaveState(ctx, ct);
await ctx.Navigation.GoToAsync<FilesPage>(ctx, ct);
break;
}
case "back":
{
await ctx.Navigation.GoToAsync<TitlePage>(ctx, ct);
break;
}
};
}
public override async Task OnText(PageContext ctx, string text, CancellationToken ct)
{
Request.Details = text;
await SaveState(ctx, ct);
await ctx.Navigation.GoToAsync<FilesPage>(ctx, ct);
}
}
/// <summary>

View File

@@ -6,8 +6,9 @@ namespace Demo.Pages;
/// <summary>
/// Страница загрузки файлов.
/// Обычная страница с полученим и сохранением состояния.
/// </summary>
public sealed class FilesPage : Page
public sealed class FilesPage : SingletonPage
{
public override Task OnEnter(PageContext ctx, CancellationToken ct)
=> new MessageBuilder(ctx)
@@ -22,6 +23,12 @@ public sealed class FilesPage : Page
await ctx.SendFileAsync(file, $"Файл '{file.Name}' получен и отправлен обратно.", ct);
}
//Обращение через Storage
var request = await ctx.StateStorage.GetAsync<Models.Request>(ctx.SessionKey, "Request", ct);
request.FilesCount = files.Count;
//Обращение через Context
await ctx.SetStorageAsync("Request", request, ct);
await new MessageBuilder(ctx)
.Text($"Получено файлов: {files.Count}", MessageFormat.Plain)
.Inline("Далее", "next")

View File

@@ -5,14 +5,25 @@ using BotPages.Core.Messaging;
namespace Demo.Pages;
/// <summary>
/// Страница ввода заголовка заявки.
/// Обычная страница вводом текста.
/// </summary>
public sealed class TitlePage : SingletonPage
{
public override Task OnEnter(PageContext ctx, CancellationToken ct)
=> new MessageBuilder(ctx)
.Text("Введите заголовок заявки:", MessageFormat.Markdown)
.Reply("Меню")
.SendAsync(ct);
public override Task OnText(PageContext ctx, string text, CancellationToken ct)
=> ctx.Navigation.GoToAsync<DetailsPage, DetailsArgs>(ctx, new DetailsArgs { Title = text }, ct);
{
if (text == "Меню")
{
return ctx.Navigation.GoToHome(ctx, ct);
}
else
{
return ctx.Navigation.GoToAsync<DetailsPage, DetailsArgs>(ctx, new DetailsArgs { Title = text }, ct);
}
}
}

View File

@@ -6,16 +6,21 @@ namespace Demo.Pages;
/// <summary>
/// Стартовая страница демо‑бота.
/// Обычная страница с кнопками
/// </summary>
public sealed class WelcomePage : SingletonPage
{
public override Task OnEnter(PageContext ctx, CancellationToken ct)
=> new MessageBuilder(ctx)
public override async Task OnEnter(PageContext ctx, CancellationToken ct)
{
await ctx.ClearStorageAsync(ct);
await new MessageBuilder(ctx)
.Text("Добро пожаловать! 🚀")
.Reply(WelcomePageButtons.CreateRequest)
.Reply(WelcomePageButtons.Help)
.Reply(WelcomePageButtons.SendFile)
.SendAsync(ct);
}
public override Task OnText(PageContext ctx, string text, CancellationToken ct)
{