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) {