refactor(tgbot-front): moved the navigation handler to a separate file

This commit is contained in:
Kirill 2025-12-05 23:42:30 +03:00
parent a22c96e7a0
commit 19164b8d9d
3 changed files with 150 additions and 145 deletions

View file

@ -71,7 +71,7 @@ private:
/// @param userId Идентификатор пользователя
/// @param payload Полезная нагрузка
/// @return HandlerResult
static HandlerResult returnMyTitles(int64_t userId, int64_t payload);
/// static HandlerResult returnMyTitles(int64_t userId, int64_t payload);
/// @brief Вход в новое состояние
/// @param ctx текущий контекст

View file

@ -0,0 +1,147 @@
#include "handlers.hpp"
#include "KeyboardFactory.hpp"
#include "structs.hpp"
#include "constants.hpp"
void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext& ctx) {
const auto& current = ctx.history.back(); // текущий экран
const std::string& data = query->data;
// Пагинация (в списках)
if ((data == BotConstants::Callback::LIST_PREV || data == BotConstants::Callback::LIST_NEXT)
&& (current.state == UserState::VIEWING_MY_TITLES || current.state == UserState::VIEWING_REVIEW_LIST ||
current.state == UserState::VIEWING_FOUND_TITLES)) {
int64_t newPayload = current.payload;
if (data == BotConstants::Callback::LIST_PREV && newPayload > 0) {
reducePayload(newPayload, current.state);
} else if (data == BotConstants::Callback::LIST_NEXT) {
increasePayload(newPayload, current.state);
} else {
if (data == BotConstants::Callback::LIST_PREV) {
std::cout << "Error: navigation:prev callback for 1st page" << std::endl;
return;
}
// TODO: log
std::cout << "Error: navigation:prev unknown error" << std::endl;
}
ctx.history.back().payload = newPayload;
auto result = renderCurrent(query, ctx);
if(result.message == "meow") return; // TODO: убрать
editMessage(query, result);
return;
}
// Обработка back по интерфейсу
if (data == BotConstants::Callback::NAV_BACK) {
if (!popState(ctx)) {
sendError(query, BotConstants::Text::SAD_ERROR);
return;
}
auto result = renderCurrent(query, ctx);
if(result.message == "meow") return; // TODO: убрать
editMessage(query, result);
return;
}
// Переходы вперёд (push)
auto newStepOpt = computeNextStep(query, current);
if (!newStepOpt.has_value()) {
sendError(query, BotConstants::Text::SAD_ERROR);
return;
}
ctx.history.push_back(*newStepOpt);
auto result = renderCurrent(query, ctx);
if(result.message == "meow") return; // TODO: убрать
editMessage(query, result);
}
HandlerResult BotHandlers::renderCurrent(TgBot::CallbackQuery::Ptr query, const UserContext& ctx) {
const auto& step = ctx.history.back();
int64_t userId = query->from->id;
switch (step.state) {
case UserState::MAIN_MENU:
return showMainMenu();
case UserState::VIEWING_MY_TITLES:
server_.fetchUserTitlesAsync(std::to_string(2)) // ALARM: тестовое значение вместо userId
.then([this, query](pplx::task<std::vector<BotStructs::Title>> t) {
try {
auto titles = t.get();
std::string message = formatTitlesList(titles);
auto keyboard = KeyboardFactory::createMyTitles(titles);
editMessage(query, {message, keyboard});
} catch (const std::exception& e) {
sendError(query, BotConstants::Text::SERVER_ERROR);
// Логирование ошибки (например, в cerr)
}
});
return {"meow", nullptr};
/*
case UserState::VIEWING_TITLE_PAGE:
return returnTitlePage(step.payload); // payload = titleId
case UserState::VIEWING_REVIEW:
return returnReview(step.payload); // payload = reviewId
case UserState::AWAITING_REVIEW:
return HandlerResult{"Пришлите текст отзыва:", nullptr};
// ...
*/
default:
return HandlerResult{BotConstants::Text::SAD_ERROR, nullptr};
}
}
std::optional<NavigationStep> BotHandlers::computeNextStep(
const TgBot::CallbackQuery::Ptr& query,
const NavigationStep& current
) {
const std::string& data = query->data;
switch (current.state) {
case UserState::MAIN_MENU:
if (data == BotConstants::Callback::MY_TITLES) {
return NavigationStep{UserState::VIEWING_MY_TITLES, 0};
}
break;
/*
case UserState::VIEWING_MY_TITLES:
if (data.starts_with("title_")) {
int64_t titleId = parseId(data);
return NavigationStep{UserState::VIEWING_TITLE_PAGE, titleId};
}
break;
case UserState::VIEWING_TITLE_PAGE:
if (data == BotConstants::Callback::ACTION_ADD_REVIEW) {
return NavigationStep{UserState::AWAITING_REVIEW, current.payload};
}
if (data.starts_with("review_")) {
int64_t reviewId = parseId(data);
return NavigationStep{UserState::VIEWING_REVIEW, reviewId};
}
break;
*/
default:
break;
}
return std::nullopt;
}
std::string BotHandlers::formatTitlesList(const std::vector<BotStructs::Title>& titles) {
if (titles.empty()) {
return "У вас пока нет тайтлов.";
}
std::string msg;
for (size_t i = 0; i < titles.size(); ++i) {
// num — 0-based, но в сообщении показываем 1-based
msg += std::to_string(i + 1) + ". " + titles[i].name + "\n";
}
return msg;
}

View file

@ -20,6 +20,7 @@ void BotHandlers::handleCallback(TgBot::CallbackQuery::Ptr query) {
processCallbackImpl(query);
}
/* deprecated. will be deleted soon.
HandlerResult BotHandlers::returnMyTitles(int64_t userId, int64_t payload) {
// Здесь должен происходить запрос на сервер
std::vector<BotStructs::Title> titles = {{123, "Школа мертвяков", "", 1}, {321, "KissXsis", "", 2}};
@ -29,7 +30,7 @@ HandlerResult BotHandlers::returnMyTitles(int64_t userId, int64_t payload) {
result.message = "1. Школа мертвяков\n2. KissXsis\n";
return result;
}
}*/
void BotHandlers::handleMessage(TgBot::Message::Ptr message) {
//TODO: просмотр состояния пользователя
@ -60,62 +61,6 @@ void BotHandlers::processCallbackImpl(TgBot::CallbackQuery::Ptr query) {
}
}
void BotHandlers::handleNavigation(TgBot::CallbackQuery::Ptr query, UserContext& ctx) {
const auto& current = ctx.history.back(); // текущий экран
const std::string& data = query->data;
// Пагинация (в списках)
if ((data == BotConstants::Callback::LIST_PREV || data == BotConstants::Callback::LIST_NEXT)
&& (current.state == UserState::VIEWING_MY_TITLES || current.state == UserState::VIEWING_REVIEW_LIST ||
current.state == UserState::VIEWING_FOUND_TITLES)) {
int64_t newPayload = current.payload;
if (data == BotConstants::Callback::LIST_PREV && newPayload > 0) {
reducePayload(newPayload, current.state);
} else if (data == BotConstants::Callback::LIST_NEXT) {
increasePayload(newPayload, current.state);
} else {
if (data == BotConstants::Callback::LIST_PREV) {
std::cout << "Error: navigation:prev callback for 1st page" << std::endl;
return;
}
// TODO: log
std::cout << "Error: navigation:prev unknown error" << std::endl;
}
ctx.history.back().payload = newPayload;
auto result = renderCurrent(query, ctx);
if(result.message == "meow") return; // TODO: убрать
editMessage(query, result);
return;
}
// Обработка back по интерфейсу
if (data == BotConstants::Callback::NAV_BACK) {
if (!popState(ctx)) {
sendError(query, BotConstants::Text::SAD_ERROR);
return;
}
auto result = renderCurrent(query, ctx);
if(result.message == "meow") return; // TODO: убрать
editMessage(query, result);
return;
}
// Переходы вперёд (push)
auto newStepOpt = computeNextStep(query, current);
if (!newStepOpt.has_value()) {
sendError(query, BotConstants::Text::SAD_ERROR);
return;
}
ctx.history.push_back(*newStepOpt);
auto result = renderCurrent(query, ctx);
if(result.message == "meow") return; // TODO: убрать
editMessage(query, result);
}
void BotHandlers::pushState(UserContext& ctx, UserState newState, int64_t payload) {
ctx.history.push_back({newState, payload});
}
@ -164,44 +109,6 @@ void BotHandlers::editMessage(TgBot::CallbackQuery::Ptr query, HandlerResult res
);
}
HandlerResult BotHandlers::renderCurrent(TgBot::CallbackQuery::Ptr query, const UserContext& ctx) {
const auto& step = ctx.history.back();
int64_t userId = query->from->id;
switch (step.state) {
case UserState::MAIN_MENU:
return showMainMenu();
case UserState::VIEWING_MY_TITLES:
server_.fetchUserTitlesAsync(std::to_string(2)) // ALARM: тестовое значение вместо userId
.then([this, query](pplx::task<std::vector<BotStructs::Title>> t) {
try {
auto titles = t.get();
std::string message = formatTitlesList(titles);
auto keyboard = KeyboardFactory::createMyTitles(titles);
editMessage(query, {message, keyboard});
} catch (const std::exception& e) {
sendError(query, BotConstants::Text::SERVER_ERROR);
// Логирование ошибки (например, в cerr)
}
});
return {"meow", nullptr};
/*
case UserState::VIEWING_TITLE_PAGE:
return returnTitlePage(step.payload); // payload = titleId
case UserState::VIEWING_REVIEW:
return returnReview(step.payload); // payload = reviewId
case UserState::AWAITING_REVIEW:
return HandlerResult{"Пришлите текст отзыва:", nullptr};
// ...
*/
default:
return HandlerResult{BotConstants::Text::SAD_ERROR, nullptr};
}
}
HandlerResult BotHandlers::showMainMenu() {
auto keyboard = KeyboardFactory::createMainMenu();
@ -221,42 +128,6 @@ void BotHandlers::sendError(TgBot::CallbackQuery::Ptr query, const std::string&
editMessage(query, {errText, keyboard});
}
std::optional<NavigationStep> BotHandlers::computeNextStep(
const TgBot::CallbackQuery::Ptr& query,
const NavigationStep& current
) {
const std::string& data = query->data;
switch (current.state) {
case UserState::MAIN_MENU:
if (data == BotConstants::Callback::MY_TITLES) {
return NavigationStep{UserState::VIEWING_MY_TITLES, 0};
}
break;
/*
case UserState::VIEWING_MY_TITLES:
if (data.starts_with("title_")) {
int64_t titleId = parseId(data);
return NavigationStep{UserState::VIEWING_TITLE_PAGE, titleId};
}
break;
case UserState::VIEWING_TITLE_PAGE:
if (data == BotConstants::Callback::ACTION_ADD_REVIEW) {
return NavigationStep{UserState::AWAITING_REVIEW, current.payload};
}
if (data.starts_with("review_")) {
int64_t reviewId = parseId(data);
return NavigationStep{UserState::VIEWING_REVIEW, reviewId};
}
break;
*/
default:
break;
}
return std::nullopt;
}
void BotHandlers::createInitContext(int64_t chatId) {
NavigationStep init = {UserState::MAIN_MENU, BotConstants::NULL_PAYLOAD};
userContexts[chatId] = {chatId, {init}};
@ -276,17 +147,4 @@ void BotHandlers::handleError(TgBot::CallbackQuery::Ptr query, UserContext& ctx)
HandlerResult result = {BotConstants::Text::AUTH_ERROR, nullptr};
editMessage(query, result);
}
}
std::string BotHandlers::formatTitlesList(const std::vector<BotStructs::Title>& titles) {
if (titles.empty()) {
return "У вас пока нет тайтлов.";
}
std::string msg;
for (size_t i = 0; i < titles.size(); ++i) {
// num — 0-based, но в сообщении показываем 1-based
msg += std::to_string(i + 1) + ". " + titles[i].name + "\n";
}
return msg;
}