diff --git a/BotPages.Core/BotPagesApp.cs b/BotPages.Core/BotPagesApp.cs
index c9958c1..8618886 100644
--- a/BotPages.Core/BotPagesApp.cs
+++ b/BotPages.Core/BotPagesApp.cs
@@ -34,7 +34,7 @@ public sealed class BotPagesApp
///
/// Установить страницу по умолчанию.
///
- public BotPagesApp AddDefaultPage() where TPage : Page
+ public BotPagesApp AddDefaultPage() where TPage : SingletonPage
{
_navigation.AddDefaultPage();
return this;
diff --git a/BotPages.Core/Context/PageContext.cs b/BotPages.Core/Context/PageContext.cs
index a259870..9a88de7 100644
--- a/BotPages.Core/Context/PageContext.cs
+++ b/BotPages.Core/Context/PageContext.cs
@@ -22,6 +22,26 @@ public sealed class PageContext
/// Адаптер мессенджера.
public required IMessengerAdapter Adapter { get; init; }
+ //Storage
+
+ /// Получить состояние по ключу.
+ public Task GetStorageAsync(string key, CancellationToken ct)
+ => StateStorage.GetAsync(SessionKey, key, ct);
+
+ /// Сохранить состояние по ключу.
+ public Task SetStorageAsync(string key, T state, CancellationToken ct)
+ => StateStorage.SetAsync(SessionKey, key, state, ct);
+
+ /// Удалить состояние по ключу.
+ public Task RemoveStorageAsync(string key, CancellationToken ct)
+ => StateStorage.RemoveAsync(SessionKey, key, ct);
+
+ /// Удалить все состояния по ключу.
+ public Task ClearStorageAsync(CancellationToken ct)
+ => StateStorage.ClearAsync(SessionKey, ct);
+
+ //Adapter
+
///
/// Отправить текстовое сообщение.
///
diff --git a/BotPages.Core/Navigation/NavigationService.cs b/BotPages.Core/Navigation/NavigationService.cs
index 089707a..5e634a6 100644
--- a/BotPages.Core/Navigation/NavigationService.cs
+++ b/BotPages.Core/Navigation/NavigationService.cs
@@ -23,7 +23,7 @@ public sealed class NavigationService
_routes = routes;
}
- internal void AddDefaultPage() where TPage : Page
+ internal void AddDefaultPage() where TPage : SingletonPage
{
_defaultPage = typeof(TPage);
}
diff --git a/BotPages.Core/Pages/PageStateAttribute.cs b/BotPages.Core/Pages/PageStateAttribute.cs
index 5aef2d5..b982dcd 100644
--- a/BotPages.Core/Pages/PageStateAttribute.cs
+++ b/BotPages.Core/Pages/PageStateAttribute.cs
@@ -3,7 +3,7 @@
///
/// Атрибут для свойств страницы, которые должны сохраняться в StateStorage.
///
-[AttributeUsage(AttributeTargets.Property)]
+[AttributeUsage(AttributeTargets.Property | AttributeTargets.Field)]
public sealed class StatefullAttribute : Attribute
{
///
diff --git a/BotPages.Core/Pages/StatefullPage.cs b/BotPages.Core/Pages/StatefullPage.cs
index ae2fefe..c1e1a1e 100644
--- a/BotPages.Core/Pages/StatefullPage.cs
+++ b/BotPages.Core/Pages/StatefullPage.cs
@@ -21,7 +21,7 @@ public abstract class StatefullPage : Page
///
/// Загружает значения свойств из StateStorage.
///
- 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
///
/// Сохраняет значения свойств в StateStorage.
///
- 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))
{
diff --git a/Demo/Models/Request.cs b/Demo/Models/Request.cs
new file mode 100644
index 0000000..7046050
--- /dev/null
+++ b/Demo/Models/Request.cs
@@ -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; } = "";
+ }
+}
diff --git a/Demo/Pages/DetailsPage.cs b/Demo/Pages/DetailsPage.cs
index d55eb88..0dc5b82 100644
--- a/Demo/Pages/DetailsPage.cs
+++ b/Demo/Pages/DetailsPage.cs
@@ -1,27 +1,57 @@
using BotPages.Core;
using BotPages.Core.Messaging;
+using System.Xml;
namespace Demo.Pages;
///
/// Страница ввода деталей заявки.
+/// Страница с параметрами и сохранением состояния.
///
public sealed class DetailsPage : StatefullPage
{
+ [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)
+ {
+ switch (payload)
{
- "next" => ctx.Navigation.GoToAsync(ctx, ct),
- "back" => ctx.Navigation.GoToAsync(ctx, ct),
- _ => Task.CompletedTask
+ case "next":
+ {
+ await SaveState(ctx, ct);
+ await ctx.Navigation.GoToAsync(ctx, ct);
+ break;
+ }
+
+ case "back":
+ {
+ await ctx.Navigation.GoToAsync(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(ctx, ct);
+ }
}
///
diff --git a/Demo/Pages/FilesPage.cs b/Demo/Pages/FilesPage.cs
index ca1553c..54dc493 100644
--- a/Demo/Pages/FilesPage.cs
+++ b/Demo/Pages/FilesPage.cs
@@ -6,8 +6,9 @@ namespace Demo.Pages;
///
/// Страница загрузки файлов.
+/// Обычная страница с полученим и сохранением состояния.
///
-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(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")
diff --git a/Demo/Pages/TitlePage.cs b/Demo/Pages/TitlePage.cs
index 479550d..a0d7abc 100644
--- a/Demo/Pages/TitlePage.cs
+++ b/Demo/Pages/TitlePage.cs
@@ -5,14 +5,25 @@ using BotPages.Core.Messaging;
namespace Demo.Pages;
///
/// Страница ввода заголовка заявки.
+/// Обычная страница вводом текста.
///
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(ctx, new DetailsArgs { Title = text }, ct);
+ {
+ if (text == "Меню")
+ {
+ return ctx.Navigation.GoToHome(ctx, ct);
+ }
+ else
+ {
+ return ctx.Navigation.GoToAsync(ctx, new DetailsArgs { Title = text }, ct);
+ }
+ }
}
\ No newline at end of file
diff --git a/Demo/Pages/WelcomePage.cs b/Demo/Pages/WelcomePage.cs
index 14db29c..72c86fd 100644
--- a/Demo/Pages/WelcomePage.cs
+++ b/Demo/Pages/WelcomePage.cs
@@ -6,16 +6,21 @@ namespace Demo.Pages;
///
/// Стартовая страница демо‑бота.
+/// Обычная страница с кнопками
///
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)
{