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:
parent
c5a6524e16
commit
34e80f04bb
@ -1,5 +1,8 @@
|
|||||||
#include "GetProfileInfoScript.h"
|
#include "GetProfileInfoScript.h"
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
|
#include <QElapsedTimer>
|
||||||
#include <QJsonDocument>
|
#include <QJsonDocument>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
@ -55,19 +58,41 @@ void GetProfileInfoScript::doStart() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Всегда тапаем кнопку обновления — баланс на экране может быть закэширован
|
// Всегда тапаем кнопку обновления — баланс на экране может быть закэширован
|
||||||
// и не отражать актуальное состояние счёта. Лоадера у refresh нет,
|
// и не отражать актуальное состояние счёта. Лоадера у refresh нет, но во
|
||||||
// поэтому ждём фиксированно.
|
// время загрузки родительский блок временно становится content-desc="TJS"
|
||||||
|
// (без даты, без числа) — это надёжный маркер «идёт обновление». Поэтому
|
||||||
|
// после тапа поллим экран до появления распарсиваемого баланса.
|
||||||
Node refreshBtn = xmlScreenParser.findRefreshButtonOnHomeScreen();
|
Node refreshBtn = xmlScreenParser.findRefreshButtonOnHomeScreen();
|
||||||
|
std::optional<double> parsedOpt;
|
||||||
if (refreshBtn.x() > 0) {
|
if (refreshBtn.x() > 0) {
|
||||||
qDebug() << "[Dushanbe::GetProfileInfo] Tapping refresh to get up-to-date balance";
|
qDebug() << "[Dushanbe::GetProfileInfo] Tapping refresh to get up-to-date balance";
|
||||||
AdbUtils::makeTap(m_deviceId, refreshBtn.x(), refreshBtn.y());
|
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 {
|
} else {
|
||||||
qWarning() << "[Dushanbe::GetProfileInfo] Refresh button not found, parsing as-is";
|
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;
|
qDebug() << "[Dushanbe::GetProfileInfo] Parsed balance after refresh:" << parsedBalance;
|
||||||
if (parsedBalance <= 0.0) {
|
if (parsedBalance <= 0.0) {
|
||||||
m_error = "Balance is zero after refresh: " + m_deviceId + " " + m_appCode;
|
m_error = "Balance is zero after refresh: " + m_deviceId + " " + m_appCode;
|
||||||
|
|||||||
@ -185,10 +185,13 @@ bool ScreenXmlParser::isHomeScreen() {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
double ScreenXmlParser::parseBalanceFromHomeScreen() {
|
std::optional<double> ScreenXmlParser::parseBalanceFromHomeScreenOpt() {
|
||||||
// На домашнем экране: parent node content-desc="TJS\n03.04.26 - 21:46:15"
|
// На домашнем экране: parent node content-desc="TJS\n03.04.26 - 21:46:15"
|
||||||
// внутри него child node content-desc="0.00" (или "10.00")
|
// внутри него 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) {
|
for (int i = 0; i < m_nodes.size(); ++i) {
|
||||||
if (!m_nodes[i].contentDesc.startsWith("TJS")) continue;
|
if (!m_nodes[i].contentDesc.startsWith("TJS")) continue;
|
||||||
// Проверяем что это блок с датой (не просто "TJS" текст)
|
// Проверяем что это блок с датой (не просто "TJS" текст)
|
||||||
@ -205,7 +208,11 @@ double ScreenXmlParser::parseBalanceFromHomeScreen() {
|
|||||||
if (ok) return val;
|
if (ok) return val;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return 0.0;
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
double ScreenXmlParser::parseBalanceFromHomeScreen() {
|
||||||
|
return parseBalanceFromHomeScreenOpt().value_or(0.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ScreenXmlParser::parseUserNameFromHomeScreen() {
|
QString ScreenXmlParser::parseUserNameFromHomeScreen() {
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
|
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QDomElement>
|
#include <QDomElement>
|
||||||
@ -37,9 +39,20 @@ public:
|
|||||||
|
|
||||||
bool isHomeScreen();
|
bool isHomeScreen();
|
||||||
|
|
||||||
// Парсит баланс с домашнего экрана
|
// Парсит баланс с домашнего экрана. Возвращает 0.0, если блок "TJS\n<дата>"
|
||||||
|
// не найден или внутри него нет числовой ноды (это может быть как реально
|
||||||
|
// нулевой баланс, так и loading state сразу после тапа refresh). Для
|
||||||
|
// различения этих случаев используйте parseBalanceFromHomeScreenOpt().
|
||||||
double parseBalanceFromHomeScreen();
|
double parseBalanceFromHomeScreen();
|
||||||
|
|
||||||
|
// То же, что parseBalanceFromHomeScreen, но возвращает std::nullopt, когда
|
||||||
|
// экран в состоянии загрузки (родитель имеет content-desc "TJS" без даты,
|
||||||
|
// либо блок "TJS\n<дата>" найден, но числовая нода рядом отсутствует) или
|
||||||
|
// когда мы вообще не на home screen. При nullopt вызывающий код может
|
||||||
|
// подождать и попробовать снова — это безопаснее, чем интерпретировать
|
||||||
|
// loading как реальный нулевой баланс.
|
||||||
|
std::optional<double> parseBalanceFromHomeScreenOpt();
|
||||||
|
|
||||||
// Находит кнопку обновления данных в блоке "TJS\n<дата>" — правый clickable
|
// Находит кнопку обновления данных в блоке "TJS\n<дата>" — правый clickable
|
||||||
// ImageView (рядом слева сидит глазик-Button, его не трогаем).
|
// ImageView (рядом слева сидит глазик-Button, его не трогаем).
|
||||||
Node findRefreshButtonOnHomeScreen();
|
Node findRefreshButtonOnHomeScreen();
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user