Добавьте файлы проекта.
This commit is contained in:
20
BotPages.Core/Pages/ActionPlacement.cs
Normal file
20
BotPages.Core/Pages/ActionPlacement.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Тип размещения кнопки.
|
||||
/// </summary>
|
||||
public enum ActionPlacement
|
||||
{
|
||||
/// <summary>
|
||||
/// Inline‑кнопка (под сообщением).
|
||||
/// </summary>
|
||||
Inline,
|
||||
|
||||
/// <summary>
|
||||
/// Reply‑кнопка (заменяет системную клавиатуру).
|
||||
/// </summary>
|
||||
Reply
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
28
BotPages.Core/Pages/IPage.cs
Normal file
28
BotPages.Core/Pages/IPage.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Контракт страницы: экран диалога с жизненным циклом.
|
||||
/// </summary>
|
||||
public interface IPage
|
||||
{
|
||||
/// <summary>
|
||||
/// Статический идентификатор страницы.
|
||||
/// </summary>
|
||||
string Id { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при входе на страницу (рендер, приветствие).
|
||||
/// </summary>
|
||||
Task<PageResult> EnterAsync(UpdateContext ctx, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Обработка входящего события/сообщения на странице.
|
||||
/// </summary>
|
||||
Task<PageResult> HandleAsync(UpdateContext ctx, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Вызывается при выходе со страницы (очистка, финализация).
|
||||
/// </summary>
|
||||
Task ExitAsync(UpdateContext ctx, CancellationToken ct);
|
||||
}
|
||||
}
|
||||
26
BotPages.Core/Pages/IPageRegistry.cs
Normal file
26
BotPages.Core/Pages/IPageRegistry.cs
Normal file
@@ -0,0 +1,26 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Реестр страниц с доступом по идентификатору.
|
||||
/// </summary>
|
||||
public interface IPageRegistry
|
||||
{
|
||||
IPage DefaultPage { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает страницу по идентификатору.
|
||||
/// </summary>
|
||||
IPage Get(string id);
|
||||
|
||||
/// <summary>
|
||||
/// Пытается получить страницу по идентификатору.
|
||||
/// </summary>
|
||||
bool TryGet(string id, out IPage? page);
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает все зарегистрированные страницы.
|
||||
/// </summary>
|
||||
IEnumerable<IPage> All();
|
||||
IPage GetOrDefault(string id);
|
||||
}
|
||||
}
|
||||
7
BotPages.Core/Pages/NavEntry.cs
Normal file
7
BotPages.Core/Pages/NavEntry.cs
Normal file
@@ -0,0 +1,7 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Запись навигационного стека: страница и её аргументы.
|
||||
/// </summary>
|
||||
public sealed record NavEntry(string PageId, object? Args = null);
|
||||
}
|
||||
32
BotPages.Core/Pages/Page.cs
Normal file
32
BotPages.Core/Pages/Page.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Базовая реализация страницы без обязательных переопределений.
|
||||
/// </summary>
|
||||
public abstract class Page : IPage
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор страницы.
|
||||
/// </summary>
|
||||
public virtual string Id => GetType().Name;
|
||||
|
||||
/// <summary>
|
||||
/// Виртуальный метод входа; по умолчанию ничего не делает.
|
||||
/// </summary>
|
||||
public virtual Task<PageResult> EnterAsync(UpdateContext ctx, CancellationToken ct) =>
|
||||
Task.FromResult(new PageResult());
|
||||
|
||||
/// <summary>
|
||||
/// Абстрактная обработка событий; обязателен к реализации.
|
||||
/// </summary>
|
||||
public abstract Task<PageResult> HandleAsync(UpdateContext ctx, CancellationToken ct);
|
||||
|
||||
/// <summary>
|
||||
/// Виртуальный метод выхода; по умолчанию ничего не делает.
|
||||
/// </summary>
|
||||
public virtual Task ExitAsync(UpdateContext ctx, CancellationToken ct) =>
|
||||
Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
28
BotPages.Core/Pages/PageAction.cs
Normal file
28
BotPages.Core/Pages/PageAction.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Универсальное действие (кнопка), которое может быть отображено в разных клиентах.
|
||||
/// </summary>
|
||||
public sealed class PageAction
|
||||
{
|
||||
/// <summary>
|
||||
/// Текст кнопки, отображаемый пользователю.
|
||||
/// </summary>
|
||||
public string Label { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Значение (payload), которое будет передано в <see cref="UpdateContext.Text"/> при нажатии.
|
||||
/// </summary>
|
||||
public string Value { get; init; } = "";
|
||||
|
||||
/// <summary>
|
||||
/// Тип кнопки: inline или reply.
|
||||
/// </summary>
|
||||
public ActionPlacement Placement { get; init; } = ActionPlacement.Inline;
|
||||
|
||||
/// <summary>
|
||||
/// Номер ряда для макета (0 — первая строка).
|
||||
/// </summary>
|
||||
public int Row { get; init; } = 0;
|
||||
}
|
||||
}
|
||||
39
BotPages.Core/Pages/PageMessage.cs
Normal file
39
BotPages.Core/Pages/PageMessage.cs
Normal file
@@ -0,0 +1,39 @@
|
||||
|
||||
/// <summary>
|
||||
/// Параметры сообщения.
|
||||
/// </summary>
|
||||
public sealed class PageMessage
|
||||
{
|
||||
/// <summary>
|
||||
/// Текст сообщения.
|
||||
/// </summary>
|
||||
public required string Text { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Формат сообщения (Plain/Markdown/Html).
|
||||
/// </summary>
|
||||
public MessageFormat Format { get; init; } = MessageFormat.Plain;
|
||||
|
||||
/// <summary>
|
||||
/// Отправить сообщение без уведомления (тихий режим).
|
||||
/// </summary>
|
||||
public bool IsSilent { get; init; } = false;
|
||||
|
||||
public static implicit operator PageMessage(string text)
|
||||
=> new PageMessage { Text = text, Format = MessageFormat.Plain };
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Тип форматирования сообщения.
|
||||
/// </summary>
|
||||
public enum MessageFormat
|
||||
{
|
||||
/// <summary>Обычный текст без форматирования.</summary>
|
||||
Plain,
|
||||
|
||||
/// <summary>Markdown.</summary>
|
||||
Markdown,
|
||||
|
||||
/// <summary>HTML.</summary>
|
||||
Html,
|
||||
}
|
||||
21
BotPages.Core/Pages/PageNavigate.cs
Normal file
21
BotPages.Core/Pages/PageNavigate.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
|
||||
/// <summary>
|
||||
/// Параметры навигации на другую страницу.
|
||||
/// </summary>
|
||||
public sealed class PageNavigate
|
||||
{
|
||||
/// <summary>
|
||||
/// Идентификатор страницы, на которую нужно перейти.
|
||||
/// </summary>
|
||||
public required string PageId { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Дополнительные аргументы для навигации.
|
||||
/// </summary>
|
||||
public object? Args { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Заменить текущую навигацию на новую.
|
||||
/// </summary>
|
||||
public bool Replace { get; init; }
|
||||
}
|
||||
84
BotPages.Core/Pages/PageRegistry.cs
Normal file
84
BotPages.Core/Pages/PageRegistry.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Базовая реализация реестра страниц на словаре.
|
||||
/// </summary>
|
||||
public sealed class PageRegistry : IPageRegistry
|
||||
{
|
||||
private readonly Dictionary<string, IPage> _pages = new(StringComparer.Ordinal);
|
||||
private readonly IPage _defaultPage;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт реестр из набора страниц.
|
||||
/// </summary>
|
||||
public PageRegistry(IEnumerable<IPage> pages) : this(pages, pages.First())
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт реестр из набора страниц.
|
||||
/// </summary>
|
||||
public PageRegistry(IEnumerable<IPage> pages, IPage defaultPage)
|
||||
{
|
||||
foreach (var p in pages) _pages[p.Id] = p;
|
||||
_defaultPage = defaultPage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает страницу по идентификатору.
|
||||
/// </summary>
|
||||
public IPage Get(string id) => _pages[id];
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает страницу по идентификатору. Если страницы нет, возвращает дефолтную.
|
||||
/// </summary>
|
||||
public IPage GetOrDefault(string id)
|
||||
=> _pages.TryGetValue(id, out var page) ? page : _defaultPage;
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает дефолтную страницу.
|
||||
/// </summary>
|
||||
public IPage DefaultPage => _defaultPage;
|
||||
|
||||
/// <summary>
|
||||
/// Пытается получить страницу по идентификатору.
|
||||
/// </summary>
|
||||
public bool TryGet(string id, out IPage? page) => _pages.TryGetValue(id, out page);
|
||||
|
||||
/// <summary>
|
||||
/// Возвращает все зарегистрированные страницы.
|
||||
/// </summary>
|
||||
public IEnumerable<IPage> All() => _pages.Values;
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт реестр страниц из всех сборок приложения.
|
||||
/// </summary>
|
||||
public static PageRegistry CreateFromApplication(string? defaultPageId = null)
|
||||
{
|
||||
// Берём все загруженные сборки в текущем AppDomain
|
||||
var assemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
|
||||
// Находим все классы, реализующие IPage
|
||||
var pages = assemblies
|
||||
.SelectMany(a => a.GetTypes())
|
||||
.Where(t => typeof(IPage).IsAssignableFrom(t) && !t.IsAbstract && t.GetConstructor(Type.EmptyTypes) != null)
|
||||
.Select(t => (IPage)Activator.CreateInstance(t)!)
|
||||
.ToList();
|
||||
|
||||
if (pages.Count == 0)
|
||||
throw new InvalidOperationException($"В приложении не найдено ни одной страницы ({nameof(IPage)}).");
|
||||
|
||||
// Определяем страницу по умолчанию
|
||||
var defaultPage = defaultPageId != null
|
||||
? pages.FirstOrDefault(p => p.Id == defaultPageId)
|
||||
: pages.First();
|
||||
|
||||
if (defaultPage == null)
|
||||
throw new InvalidOperationException($"Не найдена страница с Id={defaultPageId}.");
|
||||
|
||||
return new PageRegistry(pages, defaultPage);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
28
BotPages.Core/Pages/PageResult.cs
Normal file
28
BotPages.Core/Pages/PageResult.cs
Normal file
@@ -0,0 +1,28 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Результат обработки страницы: текст, файлы, кнопки или навигация.
|
||||
/// </summary>
|
||||
public sealed class PageResult
|
||||
{
|
||||
/// <summary>
|
||||
/// Параметры перехода страницы, на которую нужно перейти.
|
||||
/// </summary>
|
||||
public PageNavigate? NavigateTo { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Текст сообщения (опционально).
|
||||
/// </summary>
|
||||
public PageMessage? Message { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Файлы для отправки (опционально).
|
||||
/// </summary>
|
||||
public IReadOnlyList<FileDescriptor>? Files { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Кнопки (inline или reply), которые должны быть отображены пользователю.
|
||||
/// </summary>
|
||||
public IReadOnlyList<PageAction>? Actions { get; init; }
|
||||
}
|
||||
}
|
||||
98
BotPages.Core/Pages/PageResultBuilder.cs
Normal file
98
BotPages.Core/Pages/PageResultBuilder.cs
Normal file
@@ -0,0 +1,98 @@
|
||||
namespace BotPages.Core
|
||||
{
|
||||
/// <summary>
|
||||
/// Билдер для удобного создания <see cref="PageResult"/>.
|
||||
/// Мутабельный, но итоговый объект иммутабелен.
|
||||
/// </summary>
|
||||
public sealed class PageResultBuilder
|
||||
{
|
||||
private PageNavigate? _navigateTo;
|
||||
private PageMessage? _message;
|
||||
private List<FileDescriptor>? _files;
|
||||
private List<PageAction>? _actions;
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает текст сообщения.
|
||||
/// </summary>
|
||||
public PageResultBuilder WithText(string text, MessageFormat format)
|
||||
=> WithText(new PageMessage()
|
||||
{
|
||||
Text = text,
|
||||
Format = format,
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает текст сообщения.
|
||||
/// </summary>
|
||||
public PageResultBuilder WithText(string text)
|
||||
=> WithText(new PageMessage()
|
||||
{
|
||||
Text = text,
|
||||
Format = MessageFormat.Plain,
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает текст сообщения.
|
||||
/// </summary>
|
||||
public PageResultBuilder WithText(PageMessage message)
|
||||
{
|
||||
_message = message;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет клавиатуру (набор кнопок).
|
||||
/// </summary>
|
||||
public PageResultBuilder WithKeyboard(IEnumerable<PageAction> actions)
|
||||
{
|
||||
_actions = actions?.ToList();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Добавляет файлы.
|
||||
/// </summary>
|
||||
public PageResultBuilder WithFiles(IEnumerable<FileDescriptor> files)
|
||||
{
|
||||
_files = files?.ToList();
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает навигацию на другую страницу.
|
||||
/// </summary>
|
||||
public PageResultBuilder WithNavigate(string pageId, object? args = null, bool replace = true)
|
||||
=> WithNavigate(new PageNavigate()
|
||||
{
|
||||
PageId = pageId,
|
||||
Args = args,
|
||||
Replace = replace,
|
||||
});
|
||||
|
||||
/// <summary>
|
||||
/// Устанавливает навигацию на другую страницу.
|
||||
/// </summary>
|
||||
public PageResultBuilder WithNavigate(PageNavigate navigate)
|
||||
{
|
||||
_navigateTo = navigate;
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Собирает итоговый иммутабельный <see cref="PageResult"/>.
|
||||
/// </summary>
|
||||
public PageResult Build() => new PageResult
|
||||
{
|
||||
Message = _message,
|
||||
Actions = _actions,
|
||||
Files = _files,
|
||||
NavigateTo = _navigateTo,
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Создаёт новый пустой билдер.
|
||||
/// </summary>
|
||||
public static PageResultBuilder Empty() => new PageResultBuilder();
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user