1
0
forked from BRT/arc
arc/android/dushanbe/GetProfileInfoScript.cpp

178 lines
7.4 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "GetProfileInfoScript.h"
#include <optional>
#include <QElapsedTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QRegularExpression>
#include <QThread>
#include "adb/AdbUtils.h"
#include "dao/MaterialDAO.h"
#include "dao/BankProfileDAO.h"
#include "dao/DeviceDAO.h"
#include "dump/TaskDumpRecorder.h"
#include "net/NetworkService.h"
namespace Dushanbe {
GetProfileInfoScript::GetProfileInfoScript(
QString deviceId,
QString appCode,
QObject *parent
) : CommonScript(parent),
m_deviceId(std::move(deviceId)),
m_appCode(std::move(appCode)) {
}
GetProfileInfoScript::~GetProfileInfoScript() = default;
void GetProfileInfoScript::doStart() {
DeviceInfo device = DeviceDAO::getDeviceById(m_deviceId); if (device.id.isEmpty()) device = DeviceDAO::findByAdbSerial(m_deviceId); if (!device.adbSerial.isEmpty()) m_deviceId = device.adbSerial;
const BankProfileInfo app = BankProfileDAO::getApplication(device.id, m_appCode);
if (device.id.isEmpty() || app.id == -1) {
m_error = "Device or app not found: " + m_deviceId + " " + m_appCode;
qCritical() << m_error;
emit finishedWithResult(m_error);
return;
}
const TaskDumpMeta dumpMeta{{}, 0, {}, {}, app.bankProfileId, app.phone, device.id, app.appVersion};
TaskDumpRecorder::Scope dumpScope("dushanbe", "FETCH_PROFILE_INFO", m_deviceId, dumpMeta, &m_error);
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
if (!xmlScreenParser.isHomeScreen()) {
qDebug() << "[Dushanbe::GetProfileInfo] Not on home screen, restarting app...";
if (!runAppAndGoToHomeScreen(m_deviceId, app.package, app.pinCode,
device.screenWidth, device.screenHeight)) {
m_error = "Cannot reach home screen: " + device.name + " (" + m_deviceId + ") " + m_appCode;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
}
// Всегда тапаем кнопку обновления — баланс на экране может быть закэширован
// и не отражать актуальное состояние счёта. Лоадера у 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());
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 = parsedOpt.value_or(0.0);
qDebug() << "[Dushanbe::GetProfileInfo] Parsed balance after refresh:" << parsedBalance;
// Нулевой баланс — валидное состояние счёта (пустой кошелёк), не ошибка.
// Failure-ветка выше (Balance did not load) ловит реальную проблему —
// зависший loading, когда блок "TJS\n<дата>" так и не появился.
BankProfileDAO::updateAmount(app.id, parsedBalance);
// У Душанбе все карты сидят на одном счёте, а экран деталей карты
// отдаёт placeholder "0,00 TJS" — единственный источник правды
// для materials.amount это баланс home-screen.
MaterialDAO::setAmountByApp(device.id, m_appCode, parsedBalance);
// Тапаем по кнопке меню (гамбургер, левый верхний угол)
// Это первый clickable ImageView в верхней панели
Node menuButton;
for (const Node &node : xmlScreenParser.findAllNodes()) {
if (node.className == "android.widget.ImageView" && node.clickable && node.y() < 300) {
menuButton = node;
break;
}
}
if (menuButton.x() == 0 && menuButton.y() == 0) {
m_error = "Cannot find menu button: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
AdbUtils::makeTap(m_deviceId, menuButton.x(), menuButton.y());
QThread::msleep(2000);
// Ждём боковое меню
bool sideMenuFound = false;
for (int attempt = 0; attempt < 5; ++attempt) {
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
closeGooglePlayBannerIfVisible(m_deviceId, device.screenWidth, device.screenHeight);
if (xmlScreenParser.isSideMenu()) {
sideMenuFound = true;
break;
}
QThread::msleep(1000);
}
if (!sideMenuFound) {
m_error = "Side menu not detected: " + m_deviceId;
qWarning() << m_error;
AdbUtils::tryToKillApplication(m_deviceId, app.package);
emit finishedWithResult(m_error);
return;
}
// Парсим ФИО и телефон из бокового меню
const QString fullName = xmlScreenParser.parseProfileFullName();
QString phone = xmlScreenParser.parseProfilePhone();
phone.remove(QRegularExpression("[^0-9+]"));
qDebug() << "[Dushanbe::GetProfileInfo] fullName:" << fullName << "phone:" << phone;
if (!fullName.isEmpty()) {
BankProfileDAO::updateProfileInfo(app.id, fullName, phone, "");
}
if (const QString err = createBankProfileIfNeeded(device.id, m_appCode, parsedBalance); !err.isEmpty()) {
m_error = err;
emit finishedWithResult(m_error);
return;
}
const QList<MaterialInfo> allAccounts = MaterialDAO::getAccounts(device.id, m_appCode);
QList<QPair<MaterialInfo, Node>> accountsToPost;
for (const auto &acc : allAccounts) {
if (acc.materialId.isEmpty()) {
accountsToPost.append({acc, Node()});
}
}
if (!accountsToPost.isEmpty()) {
postAccountsData(accountsToPost);
}
emit finishedWithResult(m_error);
}
} // namespace Dushanbe