Доработано 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>
/// Установить страницу по умолчанию. /// Установить страницу по умолчанию.
/// </summary> /// </summary>
public BotPagesApp AddDefaultPage<TPage>() where TPage : Page public BotPagesApp AddDefaultPage<TPage>() where TPage : SingletonPage
{ {
_navigation.AddDefaultPage<TPage>(); _navigation.AddDefaultPage<TPage>();
return this; return this;

View File

@@ -22,6 +22,26 @@ public sealed class PageContext
/// <summary>Адаптер мессенджера.</summary> /// <summary>Адаптер мессенджера.</summary>
public required IMessengerAdapter Adapter { get; init; } 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>
/// Отправить текстовое сообщение. /// Отправить текстовое сообщение.
/// </summary> /// </summary>

View File

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

View File

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

View File

@@ -21,7 +21,7 @@ public abstract class StatefullPage : Page
/// <summary> /// <summary>
/// Загружает значения свойств из StateStorage. /// Загружает значения свойств из StateStorage.
/// </summary> /// </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)) foreach (var prop in GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance))
{ {
@@ -40,7 +40,7 @@ public abstract class StatefullPage : Page
/// <summary> /// <summary>
/// Сохраняет значения свойств в StateStorage. /// Сохраняет значения свойств в StateStorage.
/// </summary> /// </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)) 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,29 +1,59 @@
using BotPages.Core; using BotPages.Core;
using BotPages.Core.Messaging; using BotPages.Core.Messaging;
using System.Xml;
namespace Demo.Pages; namespace Demo.Pages;
/// <summary> /// <summary>
/// Страница ввода деталей заявки. /// Страница ввода деталей заявки.
/// Страница с параметрами и сохранением состояния.
/// </summary> /// </summary>
public sealed class DetailsPage : StatefullPage<DetailsArgs> public sealed class DetailsPage : StatefullPage<DetailsArgs>
{ {
[Statefull("Request")]
private Models.Request Request;
public override Task OnEnter(PageContext ctx, DetailsArgs args, CancellationToken ct) 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Добавьте детали или нажмите Далее.") .Text($"Заголовок: {args.Title}\nДобавьте детали или нажмите Далее.")
.Inline(new InlineButton("Далее", "next"), new InlineButton("Назад", "back")) .Inline(new InlineButton("Далее", "next"), new InlineButton("Назад", "back"))
.Reply("Отмена") .Reply("Отмена")
.SendAsync(ct); .SendAsync(ct);
}
public override Task OnButton(PageContext ctx, string payload, CancellationToken ct) public override async Task OnButton(PageContext ctx, string payload, CancellationToken ct)
=> payload switch
{ {
"next" => ctx.Navigation.GoToAsync<FilesPage>(ctx, ct), switch (payload)
"back" => ctx.Navigation.GoToAsync<TitlePage>(ctx, ct), {
_ => Task.CompletedTask 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> /// <summary>
/// Аргументы для страницы DetailsPage. /// Аргументы для страницы DetailsPage.
/// </summary> /// </summary>

View File

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

View File

@@ -5,14 +5,25 @@ using BotPages.Core.Messaging;
namespace Demo.Pages; namespace Demo.Pages;
/// <summary> /// <summary>
/// Страница ввода заголовка заявки. /// Страница ввода заголовка заявки.
/// Обычная страница вводом текста.
/// </summary> /// </summary>
public sealed class TitlePage : SingletonPage public sealed class TitlePage : SingletonPage
{ {
public override Task OnEnter(PageContext ctx, CancellationToken ct) public override Task OnEnter(PageContext ctx, CancellationToken ct)
=> new MessageBuilder(ctx) => new MessageBuilder(ctx)
.Text("Введите заголовок заявки:", MessageFormat.Markdown) .Text("Введите заголовок заявки:", MessageFormat.Markdown)
.Reply("Меню")
.SendAsync(ct); .SendAsync(ct);
public override Task OnText(PageContext ctx, string text, CancellationToken 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>
/// Стартовая страница демо‑бота. /// Стартовая страница демо‑бота.
/// Обычная страница с кнопками
/// </summary> /// </summary>
public sealed class WelcomePage : SingletonPage public sealed class WelcomePage : SingletonPage
{ {
public override Task OnEnter(PageContext ctx, CancellationToken ct) public override async Task OnEnter(PageContext ctx, CancellationToken ct)
=> new MessageBuilder(ctx) {
await ctx.ClearStorageAsync(ct);
await new MessageBuilder(ctx)
.Text("Добро пожаловать! 🚀") .Text("Добро пожаловать! 🚀")
.Reply(WelcomePageButtons.CreateRequest) .Reply(WelcomePageButtons.CreateRequest)
.Reply(WelcomePageButtons.Help) .Reply(WelcomePageButtons.Help)
.Reply(WelcomePageButtons.SendFile) .Reply(WelcomePageButtons.SendFile)
.SendAsync(ct); .SendAsync(ct);
}
public override Task OnText(PageContext ctx, string text, CancellationToken ct) public override Task OnText(PageContext ctx, string text, CancellationToken ct)
{ {