1
0
forked from BRT/arc

Include appVersion and clientVersion in error reporting for task failures. Add fallback logic for bottom navigation taps in GetLastTransactionsScript using icon-based detection.

This commit is contained in:
slava 2026-06-02 21:56:42 +05:00
parent b486e922b3
commit 3391591e33
2 changed files with 54 additions and 29 deletions

View File

@ -1,7 +1,6 @@
#include "GetLastTransactionsScript.h"
#include <QJsonDocument>
#include <QHash>
#include <QRegularExpression>
#include <QSet>
#include <QThread>
@ -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<int>(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<int>(device.screenHeight * 0.80))
return {lbl.x(), lbl.y()};
// Узел нав-таба отсутствует или негоден. На части устройств
// (наблюдалось на Infinix X669D, app 3.2.41 — гибридный UI) uiautomator не
// отдаёт bounds нижнего меню, хотя оно отрисовано в полосе у самого низа
// экрана (ниже всего дерева доступности). Тапаем по геометрии: 5 равных
// слотов — Главная · Переводы · [FAB] · MiniApps · История; центр слота —
// доля ширины, высота — у самого низа экрана.
static const QHash<QString, double> 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<int>(device.screenWidth * it.value());
const int y = static_cast<int>(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(

View File

@ -6,6 +6,7 @@
#include <QJsonObject>
#include <QJsonArray>
#include <QMap>
#include <QSettings>
#include <QThread>
#include <QTimer>
#include <QTimeZone>
@ -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("<b>[TASK_ERROR] [%1] [%2]</b>")
.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);