1
0
forked from BRT/arc

Improve GetProfileInfoScript with polling mechanism for balance loading. Add parseBalanceFromHomeScreenOpt to differentiate loading state from zero balance. Update ScreenXmlParser logic accordingly.

This commit is contained in:
slava 2026-05-26 20:30:54 +05:00
parent c5a6524e16
commit 34e80f04bb
3 changed files with 54 additions and 9 deletions

View File

@ -1,5 +1,8 @@
#include "GetProfileInfoScript.h"
#include <optional>
#include <QElapsedTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
@ -55,19 +58,41 @@ void GetProfileInfoScript::doStart() {
}
// Всегда тапаем кнопку обновления — баланс на экране может быть закэширован
// и не отражать актуальное состояние счёта. Лоадера у refresh нет,
// поэтому ждём фиксированно.
// и не отражать актуальное состояние счёта. Лоадера у refresh нет, но во
// время загрузки родительский блок временно становится content-desc="TJS"
// (без даты, без числа) — это надёжный маркер «идёт обновление». Поэтому
// после тапа поллим экран до появления распарсиваемого баланса.
Node refreshBtn = xmlScreenParser.findRefreshButtonOnHomeScreen();
std::optional<double> parsedOpt;
if (refreshBtn.x() > 0) {
qDebug() << "[Dushanbe::GetProfileInfo] Tapping refresh to get up-to-date balance";
AdbUtils::makeTap(m_deviceId, refreshBtn.x(), refreshBtn.y());
QThread::msleep(3000);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
constexpr int kPollIntervalMs = 500;
constexpr int kPollTimeoutMs = 15000;
QElapsedTimer t;
t.start();
while (true) {
QThread::msleep(kPollIntervalMs);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
parsedOpt = xmlScreenParser.parseBalanceFromHomeScreenOpt();
if (parsedOpt.has_value()) break;
if (t.elapsed() >= kPollTimeoutMs) break;
}
if (!parsedOpt.has_value()) {
m_error = "Balance did not load after refresh: " + m_deviceId + " " + m_appCode;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
} else {
qWarning() << "[Dushanbe::GetProfileInfo] Refresh button not found, parsing as-is";
parsedOpt = xmlScreenParser.parseBalanceFromHomeScreenOpt();
}
const double parsedBalance = xmlScreenParser.parseBalanceFromHomeScreen();
const double parsedBalance = parsedOpt.value_or(0.0);
qDebug() << "[Dushanbe::GetProfileInfo] Parsed balance after refresh:" << parsedBalance;
if (parsedBalance <= 0.0) {
m_error = "Balance is zero after refresh: " + m_deviceId + " " + m_appCode;

View File

@ -185,10 +185,13 @@ bool ScreenXmlParser::isHomeScreen() {
return false;
}
double ScreenXmlParser::parseBalanceFromHomeScreen() {
std::optional<double> ScreenXmlParser::parseBalanceFromHomeScreenOpt() {
// На домашнем экране: parent node content-desc="TJS\n03.04.26 - 21:46:15"
// внутри него child node content-desc="0.00" (или "10.00")
// Стратегия: находим индекс parent, затем child сразу после него с числовым content-desc
// Стратегия: находим индекс parent, затем child сразу после него с числовым content-desc.
// Во время refresh-загрузки parent временно превращается в content-desc="TJS" (без даты)
// и числового child нет — в этом случае возвращаем nullopt, чтобы вызывающий
// код мог отличить состояние загрузки от реального нулевого баланса.
for (int i = 0; i < m_nodes.size(); ++i) {
if (!m_nodes[i].contentDesc.startsWith("TJS")) continue;
// Проверяем что это блок с датой (не просто "TJS" текст)
@ -205,7 +208,11 @@ double ScreenXmlParser::parseBalanceFromHomeScreen() {
if (ok) return val;
}
}
return 0.0;
return std::nullopt;
}
double ScreenXmlParser::parseBalanceFromHomeScreen() {
return parseBalanceFromHomeScreenOpt().value_or(0.0);
}
QString ScreenXmlParser::parseUserNameFromHomeScreen() {

View File

@ -1,5 +1,7 @@
#pragma once
#include <optional>
#include <QMap>
#include <QObject>
#include <QDomElement>
@ -37,9 +39,20 @@ public:
bool isHomeScreen();
// Парсит баланс с домашнего экрана
// Парсит баланс с домашнего экрана. Возвращает 0.0, если блок "TJS\n<дата>"
// не найден или внутри него нет числовой ноды (это может быть как реально
// нулевой баланс, так и loading state сразу после тапа refresh). Для
// различения этих случаев используйте parseBalanceFromHomeScreenOpt().
double parseBalanceFromHomeScreen();
// То же, что parseBalanceFromHomeScreen, но возвращает std::nullopt, когда
// экран в состоянии загрузки (родитель имеет content-desc "TJS" без даты,
// либо блок "TJS\n<дата>" найден, но числовая нода рядом отсутствует) или
// когда мы вообще не на home screen. При nullopt вызывающий код может
// подождать и попробовать снова — это безопаснее, чем интерпретировать
// loading как реальный нулевой баланс.
std::optional<double> parseBalanceFromHomeScreenOpt();
// Находит кнопку обновления данных в блоке "TJS\n<дата>" — правый clickable
// ImageView (рядом слева сидит глазик-Button, его не трогаем).
Node findRefreshButtonOnHomeScreen();