From 3391591e33eae5d62762a76e33d5bad7a8645ffd Mon Sep 17 00:00:00 2001 From: slava Date: Tue, 2 Jun 2026 21:56:42 +0500 Subject: [PATCH] Include `appVersion` and `clientVersion` in error reporting for task failures. Add fallback logic for bottom navigation taps in `GetLastTransactionsScript` using icon-based detection. --- .../dushanbe/GetLastTransactionsScript.cpp | 72 +++++++++++-------- services/EventHandler.cpp | 11 ++- 2 files changed, 54 insertions(+), 29 deletions(-) diff --git a/android/dushanbe/GetLastTransactionsScript.cpp b/android/dushanbe/GetLastTransactionsScript.cpp index 95319f9..585e9ae 100644 --- a/android/dushanbe/GetLastTransactionsScript.cpp +++ b/android/dushanbe/GetLastTransactionsScript.cpp @@ -1,7 +1,6 @@ #include "GetLastTransactionsScript.h" #include -#include #include #include #include @@ -437,34 +436,51 @@ void GetLastTransactionsScript::doStart() { } QPoint GetLastTransactionsScript::bottomNavPoint(const QString &label, const DeviceInfo &device) { - const Node n = xmlScreenParser.findNodeByContentDesc(label); - // Реальные bounds принимаем только если они «вменяемые» для НИЖНЕГО меню: - // непустой узел, ненулевой центр И центр в нижней полосе экрана (>= 80% - // высоты). Это отсекает не только вырожденные [0,0][0,0] (Infinix XOS), но и - // НЕнулевые, но негодные координаты — напр. одноимённый элемент не в нав-баре - // или частичные bounds. Иначе нав-бар у самого низа, центр таба ~95% высоты. - if (!n.isEmpty() && n.x() > 0 && n.y() >= static_cast(device.screenHeight * 0.80)) - return {n.x(), n.y()}; + // 1) Норма: у таба есть реальные bounds в нижней полосе экрана. Проверка + // «центр в нижних 80% высоты» отсекает и вырожденные [0,0][0,0], и + // НЕнулевые-но-негодные координаты (одноимённый элемент не в нав-баре). + const Node lbl = xmlScreenParser.findNodeByContentDesc(label); + if (!lbl.isEmpty() && lbl.x() > 0 + && lbl.y() >= static_cast(device.screenHeight * 0.80)) + return {lbl.x(), lbl.y()}; - // Узел нав-таба отсутствует или негоден. На части устройств - // (наблюдалось на Infinix X669D, app 3.2.41 — гибридный UI) uiautomator не - // отдаёт bounds нижнего меню, хотя оно отрисовано в полосе у самого низа - // экрана (ниже всего дерева доступности). Тапаем по геометрии: 5 равных - // слотов — Главная · Переводы · [FAB] · MiniApps · История; центр слота — - // доля ширины, высота — у самого низа экрана. - static const QHash kFx{ - {QString::fromUtf8("Главная"), 0.10}, - {QString::fromUtf8("Переводы"), 0.30}, - {QString::fromUtf8("MiniApps"), 0.70}, - {QString::fromUtf8("История"), 0.90}, - }; - const auto it = kFx.constFind(label); - if (it == kFx.constEnd()) return {-1, -1}; - const int x = static_cast(device.screenWidth * it.value()); - const int y = static_cast(device.screenHeight * 0.95); - qWarning() << "[Dushanbe::GetLastTransactions] nav tab" << label - << "has no usable bounds — geometric fallback tap at" << x << y; - return {x, y}; + // 2) Лейбл нав-таба вырожден ([0,0][0,0] — гибридный UI, напр. Infinix XOS не + // отдаёт bounds нижнего меню). Но ИКОНКИ табов имеют реальные координаты. + // Берём самый нижний ряд небольших нод и в нём крайнюю по X: История — + // самая правая иконка, Главная — самая левая (FAB по центру никогда не + // крайний; контентные плитки выше нижнего ряда и отсекаются по y2). Это + // фактические координаты → не зависит от размера экрана, плотности и + // высоты системной навигации Android (её фикс-высота в dp ломала бы доли). + const bool rightmost = (label == QString::fromUtf8("История")); + const bool leftmost = (label == QString::fromUtf8("Главная")); + if (rightmost || leftmost) { + const int H = device.screenHeight, W = device.screenWidth; + const auto nodes = xmlScreenParser.findAllNodes(); + auto isNavIcon = [&](const Node &n) { + return n.x1() >= 0 && n.width() > 0 && n.height() >= 12 + && n.width() <= W / 3 && n.y() >= H / 2; // не контейнер, нижняя половина + }; + int rowBottom = -1; + for (const Node &n : nodes) + if (isNavIcon(n)) rowBottom = qMax(rowBottom, n.y2()); + if (rowBottom > 0) { + const int tol = qMax(8, H / 100); // «тот же ряд» — у самой нижней кромки + Node pick; + for (const Node &n : nodes) { + if (!isNavIcon(n) || n.y2() < rowBottom - tol) continue; + if (pick.isEmpty() + || (rightmost && n.x() > pick.x()) + || (leftmost && n.x() < pick.x())) + pick = n; + } + if (!pick.isEmpty()) { + qWarning() << "[Dushanbe::GetLastTransactions] nav tab" << label + << "— icon-row fallback tap at" << pick.x() << pick.y(); + return {pick.x(), pick.y()}; + } + } + } + return {-1, -1}; } void GetLastTransactionsScript::searchAll( diff --git a/services/EventHandler.cpp b/services/EventHandler.cpp index 08e6229..16ee2a6 100644 --- a/services/EventHandler.cpp +++ b/services/EventHandler.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -230,27 +231,35 @@ static void sendTaskError(const QString &type, const QString &materialId, const // api-токен общий на нескольких операторов — по нему не отличить, чей // профиль упал. Резолвим банковский профиль по accountId и пишем его // серверный UUID + материал + телефон профиля. - QString profileUuid, profilePhone; + QString profileUuid, profilePhone, appVersion; if (accountId > 0) { const MaterialInfo acc = MaterialDAO::getAccountById(accountId); if (acc.id != -1) { const BankProfileInfo app = BankProfileDAO::getApplication(acc.deviceId, acc.appCode); profileUuid = app.bankProfileId; profilePhone = app.phone; + appVersion = app.appVersion; } } const QString desktopId = SettingsDAO::get("desktop_id"); + // Версия самого ARC-десктопа (клиента), не банковского приложения. + const QString clientVersion = + QSettings("config.ini", QSettings::IniFormat).value("common/version").toString(); QStringList lines; lines << QStringLiteral("[TASK_ERROR] [%1] [%2]") .arg(type.toHtmlEscaped(), bankName.toUpper().toHtmlEscaped()); if (!desktopId.isEmpty()) lines << QStringLiteral("Desktop: %1").arg(desktopId.toHtmlEscaped()); + if (!clientVersion.isEmpty()) + lines << QStringLiteral("DesktopVersion: %1").arg(clientVersion.toHtmlEscaped()); if (!profileUuid.isEmpty()) lines << QStringLiteral("Profile: %1").arg(profileUuid.toHtmlEscaped()); if (!materialId.isEmpty()) lines << QStringLiteral("Material: %1").arg(materialId.toHtmlEscaped()); if (!profilePhone.isEmpty()) lines << QStringLiteral("Phone: %1").arg(profilePhone.toHtmlEscaped()); + if (!appVersion.isEmpty()) + lines << QStringLiteral("AppVersion: %1").arg(appVersion.toHtmlEscaped()); lines << QStringLiteral("Error: %1").arg(description.toHtmlEscaped()); const QString message = lines.join(QStringLiteral("\n")); sendMonitoringLog("error", "task_error", message, screenshot, eventId);