diff --git a/android/dushanbe/GetProfileInfoScript.cpp b/android/dushanbe/GetProfileInfoScript.cpp index 0fab758..36d89b7 100644 --- a/android/dushanbe/GetProfileInfoScript.cpp +++ b/android/dushanbe/GetProfileInfoScript.cpp @@ -1,5 +1,8 @@ #include "GetProfileInfoScript.h" +#include + +#include #include #include #include @@ -55,19 +58,41 @@ void GetProfileInfoScript::doStart() { } // Всегда тапаем кнопку обновления — баланс на экране может быть закэширован - // и не отражать актуальное состояние счёта. Лоадера у refresh нет, - // поэтому ждём фиксированно. + // и не отражать актуальное состояние счёта. Лоадера у refresh нет, но во + // время загрузки родительский блок временно становится content-desc="TJS" + // (без даты, без числа) — это надёжный маркер «идёт обновление». Поэтому + // после тапа поллим экран до появления распарсиваемого баланса. Node refreshBtn = xmlScreenParser.findRefreshButtonOnHomeScreen(); + std::optional 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; diff --git a/android/dushanbe/ScreenXmlParser.cpp b/android/dushanbe/ScreenXmlParser.cpp index a49f71e..b3e4038 100644 --- a/android/dushanbe/ScreenXmlParser.cpp +++ b/android/dushanbe/ScreenXmlParser.cpp @@ -185,10 +185,13 @@ bool ScreenXmlParser::isHomeScreen() { return false; } -double ScreenXmlParser::parseBalanceFromHomeScreen() { +std::optional 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() { diff --git a/android/dushanbe/ScreenXmlParser.h b/android/dushanbe/ScreenXmlParser.h index 56bf00f..2eea5bd 100644 --- a/android/dushanbe/ScreenXmlParser.h +++ b/android/dushanbe/ScreenXmlParser.h @@ -1,5 +1,7 @@ #pragma once +#include + #include #include #include @@ -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 parseBalanceFromHomeScreenOpt(); + // Находит кнопку обновления данных в блоке "TJS\n<дата>" — правый clickable // ImageView (рядом слева сидит глазик-Button, его не трогаем). Node findRefreshButtonOnHomeScreen();