538 lines
23 KiB
C++
538 lines
23 KiB
C++
#include "GetLastTransactionsScript.h"
|
||
|
||
#include <QRegularExpression>
|
||
#include <QThread>
|
||
#include <QTimeZone>
|
||
|
||
#include <tesseract/baseapi.h>
|
||
#include <leptonica/allheaders.h>
|
||
|
||
#include "adb/AdbUtils.h"
|
||
#include "dao/MaterialDAO.h"
|
||
#include "dao/BankProfileDAO.h"
|
||
#include "dao/DeviceDAO.h"
|
||
#include "dao/TransactionDAO.h"
|
||
#include "AppLogger.h"
|
||
|
||
#include <QCoreApplication>
|
||
static QString recognizeTextFromImage(const QByteArray &pngData) {
|
||
auto *api = new tesseract::TessBaseAPI();
|
||
if (api->Init(QCoreApplication::applicationDirPath().toUtf8().constData(), "rus+eng")) {
|
||
qWarning() << "[Dushanbe::OCR] Failed to init Tesseract";
|
||
delete api;
|
||
return {};
|
||
}
|
||
Pix *pix = pixReadMem(reinterpret_cast<const l_uint8 *>(pngData.constData()),
|
||
static_cast<size_t>(pngData.size()));
|
||
if (!pix) {
|
||
api->End(); delete api;
|
||
return {};
|
||
}
|
||
api->SetImage(pix);
|
||
char *text = api->GetUTF8Text();
|
||
QString result = QString::fromUtf8(text).trimmed();
|
||
delete[] text;
|
||
pixDestroy(&pix);
|
||
api->End();
|
||
delete api;
|
||
return result;
|
||
}
|
||
|
||
// Точечный OCR: находит "Номер операции" через общий OCR, затем кропает строку правее
|
||
// и распознаёт только цифры. Работает с любым разрешением экрана.
|
||
static QString recognizeOperationId(const QByteArray &pngData) {
|
||
Pix *pix = pixReadMem(reinterpret_cast<const l_uint8 *>(pngData.constData()),
|
||
static_cast<size_t>(pngData.size()));
|
||
if (!pix) return {};
|
||
|
||
const int w = pixGetWidth(pix);
|
||
const int h = pixGetHeight(pix);
|
||
|
||
// 1. Полный OCR с координатами слов — ищем "операции"
|
||
auto *api = new tesseract::TessBaseAPI();
|
||
if (api->Init(QCoreApplication::applicationDirPath().toUtf8().constData(), "rus+eng")) {
|
||
pixDestroy(&pix); delete api;
|
||
return {};
|
||
}
|
||
api->SetImage(pix);
|
||
api->Recognize(nullptr);
|
||
|
||
int opLineY = -1;
|
||
int opLineH = 0;
|
||
bool prevWasNomer = false;
|
||
tesseract::ResultIterator *ri = api->GetIterator();
|
||
if (ri) {
|
||
do {
|
||
const char *word = ri->GetUTF8Text(tesseract::RIL_WORD);
|
||
if (word) {
|
||
const QString w_str = QString::fromUtf8(word);
|
||
delete[] word;
|
||
if (w_str.contains(QString::fromUtf8("омер")) ||
|
||
w_str.contains(QString::fromUtf8("omep"))) {
|
||
prevWasNomer = true;
|
||
continue;
|
||
}
|
||
if (prevWasNomer &&
|
||
(w_str.contains(QString::fromUtf8("операции")) ||
|
||
w_str.contains(QString::fromUtf8("onepaции")))) {
|
||
int x1, y1, x2, y2;
|
||
ri->BoundingBox(tesseract::RIL_WORD, &x1, &y1, &x2, &y2);
|
||
opLineY = y1;
|
||
opLineH = y2 - y1;
|
||
break;
|
||
}
|
||
prevWasNomer = false;
|
||
}
|
||
} while (ri->Next(tesseract::RIL_WORD));
|
||
}
|
||
api->End();
|
||
delete api;
|
||
|
||
if (opLineY < 0) {
|
||
pixDestroy(&pix);
|
||
return {};
|
||
}
|
||
|
||
// 2. Кропаем правую половину этой строки и OCR только цифры
|
||
const int cropY = qMax(0, opLineY - opLineH / 2);
|
||
const int cropH = opLineH * 2;
|
||
BOX *box = boxCreate(w / 2, cropY, w / 2, qMin(cropH, h - cropY));
|
||
Pix *cropped = pixClipRectangle(pix, box, nullptr);
|
||
boxDestroy(&box);
|
||
pixDestroy(&pix);
|
||
|
||
if (!cropped) return {};
|
||
|
||
auto *api2 = new tesseract::TessBaseAPI();
|
||
if (api2->Init(QCoreApplication::applicationDirPath().toUtf8().constData(), "eng")) {
|
||
pixDestroy(&cropped); delete api2;
|
||
return {};
|
||
}
|
||
api2->SetVariable("tessedit_char_whitelist", "0123456789");
|
||
api2->SetPageSegMode(tesseract::PSM_SINGLE_LINE);
|
||
api2->SetImage(cropped);
|
||
char *text = api2->GetUTF8Text();
|
||
QString result = QString::fromUtf8(text).trimmed();
|
||
delete[] text;
|
||
pixDestroy(&cropped);
|
||
api2->End();
|
||
delete api2;
|
||
|
||
result.remove(QRegularExpression("[^0-9]"));
|
||
if (result.length() >= 8) {
|
||
qDebug() << "[Dushanbe::OCR] Precise operation ID:" << result;
|
||
return result;
|
||
}
|
||
return {};
|
||
}
|
||
|
||
struct OcrTransactionDetail {
|
||
QString date;
|
||
QString time;
|
||
QString operationId;
|
||
QString provider;
|
||
QString senderAccount;
|
||
QString receiverAccount;
|
||
double amount = 0.0;
|
||
double fee = 0.0;
|
||
QString status;
|
||
};
|
||
|
||
static OcrTransactionDetail parseDetailFromOcr(const QString &ocrText) {
|
||
OcrTransactionDetail d;
|
||
const QStringList lines = ocrText.split('\n');
|
||
|
||
for (const QString &rawLine : lines) {
|
||
const QString line = rawLine.trimmed();
|
||
|
||
// "Дата операции: 05.04.2026" или "Дата операции:" на отдельной строке
|
||
if (line.contains(QString::fromUtf8("Дата операции"))) {
|
||
QRegularExpression re(R"((\d{2}\.\d{2}\.\d{4}))");
|
||
if (auto m = re.match(line); m.hasMatch()) d.date = m.captured(1);
|
||
}
|
||
// "Время операции: 18:19:54"
|
||
else if (line.contains(QString::fromUtf8("Время операции"))) {
|
||
QRegularExpression re(R"((\d{2}:\d{2}:\d{2}))");
|
||
if (auto m = re.match(line); m.hasMatch()) d.time = m.captured(1);
|
||
}
|
||
// "Номер операции: 1598279763"
|
||
else if (line.contains(QString::fromUtf8("омер операции"))) {
|
||
QRegularExpression re(R"((\d{5,}))");
|
||
if (auto m = re.match(line); m.hasMatch()) d.operationId = m.captured(1);
|
||
}
|
||
// "Поставщик: DC (по номеру карты)"
|
||
else if (line.contains(QString::fromUtf8("Поставщик"))) {
|
||
QRegularExpression re(R"(Поставщик[:\s]*(.+))");
|
||
if (auto m = re.match(line); m.hasMatch()) d.provider = m.captured(1).trimmed();
|
||
}
|
||
// "Счет отправителя: 9762***3258"
|
||
else if (line.contains(QString::fromUtf8("Счет отправителя")) ||
|
||
line.contains(QString::fromUtf8("чет отправителя"))) {
|
||
QRegularExpression re(R"((\d[\d*]+))");
|
||
if (auto m = re.match(line); m.hasMatch()) d.senderAccount = m.captured(1);
|
||
}
|
||
// "Счет получателя: 9762000186263942" или "992111100664"
|
||
else if (line.contains(QString::fromUtf8("Счет получателя")) ||
|
||
line.contains(QString::fromUtf8("чет получателя"))) {
|
||
QRegularExpression re(R"((\d{6,}))");
|
||
if (auto m = re.match(line); m.hasMatch()) d.receiverAccount = m.captured(1);
|
||
}
|
||
// "Сумма операции: 1.00"
|
||
else if (line.contains(QString::fromUtf8("Сумма операции")) ||
|
||
line.contains(QString::fromUtf8("умма операции"))) {
|
||
QRegularExpression re(R"((\d+[\.,]\d{2}))");
|
||
if (auto m = re.match(line); m.hasMatch()) {
|
||
QString val = m.captured(1);
|
||
val.replace(',', '.');
|
||
d.amount = val.toDouble();
|
||
}
|
||
}
|
||
// "Комиссия: 0.00"
|
||
else if (line.contains(QString::fromUtf8("Комиссия")) ||
|
||
line.contains(QString::fromUtf8("омиссия"))) {
|
||
QRegularExpression re(R"((\d+[\.,]\d{2}))");
|
||
if (auto m = re.match(line); m.hasMatch()) {
|
||
QString val = m.captured(1);
|
||
val.replace(',', '.');
|
||
d.fee = val.toDouble();
|
||
}
|
||
}
|
||
// "Статус: Успешный"
|
||
else if (line.contains(QString::fromUtf8("Статус"))) {
|
||
if (line.contains(QString::fromUtf8("Успешн"))) {
|
||
d.status = QString::fromUtf8("Успешный");
|
||
}
|
||
}
|
||
}
|
||
|
||
// Fallback: если дата/время не найдены в "ключ: значение", ищем паттерном
|
||
if (d.date.isEmpty()) {
|
||
QRegularExpression re(R"((\d{2}\.\d{2}\.\d{4}))");
|
||
if (auto m = re.match(ocrText); m.hasMatch()) d.date = m.captured(1);
|
||
}
|
||
if (d.time.isEmpty()) {
|
||
QRegularExpression re(R"((\d{2}:\d{2}:\d{2}))");
|
||
auto it = re.globalMatch(ocrText);
|
||
QStringList times;
|
||
while (it.hasNext()) times.append(it.next().captured(1));
|
||
if (times.size() >= 2) d.time = times[1];
|
||
else if (times.size() == 1) d.time = times[0];
|
||
}
|
||
|
||
if (ocrText.contains(QString::fromUtf8("Успешн"))) d.status = QString::fromUtf8("Успешный");
|
||
|
||
return d;
|
||
}
|
||
|
||
namespace Dushanbe {
|
||
|
||
GetLastTransactionsScript::GetLastTransactionsScript(
|
||
QString deviceId,
|
||
QString appCode,
|
||
double searchAmount,
|
||
QDateTime searchTime,
|
||
QObject *parent
|
||
) : CommonScript(parent),
|
||
m_deviceId(std::move(deviceId)),
|
||
m_appCode(std::move(appCode)),
|
||
m_searchAmount(searchAmount),
|
||
m_searchTime(std::move(searchTime)) {
|
||
}
|
||
|
||
GetLastTransactionsScript::~GetLastTransactionsScript() = default;
|
||
|
||
void GetLastTransactionsScript::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;
|
||
}
|
||
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
if (!xmlScreenParser.isHomeScreen()) {
|
||
qDebug() << "[Dushanbe::GetLastTransactions] 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 + ")";
|
||
qWarning() << m_error;
|
||
AppLogger::log("dushanbe/GetLastTransactions", m_deviceId, m_error, {}, "FETCH_TRANSACTIONS");
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
}
|
||
|
||
// Тапаем "История" в нижнем меню
|
||
Node historyBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("История"));
|
||
if (historyBtn.x() == 0 && historyBtn.y() == 0) {
|
||
m_error = "Cannot find History button: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
AdbUtils::makeTap(m_deviceId, historyBtn.x(), historyBtn.y());
|
||
QThread::msleep(3000);
|
||
|
||
// Ждём экран истории
|
||
bool historyLoaded = false;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
// Проверяем диалог "Ошибка" — нажимаем "ОК" и обновляем свайпом вниз
|
||
if (!xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Ошибка")).isEmpty()) {
|
||
Node okBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("ОК"));
|
||
if (!okBtn.isEmpty()) {
|
||
qDebug() << "[Dushanbe::GetLastTransactions] Error dialog detected, tapping OK";
|
||
AdbUtils::makeTap(m_deviceId, okBtn.x(), okBtn.y());
|
||
QThread::msleep(1000);
|
||
}
|
||
// Свайп вниз для обновления (pull-to-refresh)
|
||
const int x = device.screenWidth / 2;
|
||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight / 4, x, device.screenHeight * 3 / 4);
|
||
QThread::msleep(3000);
|
||
continue;
|
||
}
|
||
|
||
if (xmlScreenParser.isHistoryScreen()) {
|
||
historyLoaded = true;
|
||
break;
|
||
}
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!historyLoaded) {
|
||
m_error = "History screen not loaded: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
|
||
// Переключаемся на вкладку "Выписка" (по умолчанию открыта "Операции")
|
||
Node vypiskaBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Выписка"));
|
||
if (vypiskaBtn.isEmpty()) {
|
||
m_error = "Cannot find 'Выписка' tab: " + m_deviceId;
|
||
qWarning() << m_error;
|
||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||
emit finishedWithResult(m_error);
|
||
return;
|
||
}
|
||
qDebug() << "[Dushanbe::GetLastTransactions] Switching to 'Выписка' tab";
|
||
AdbUtils::makeTap(m_deviceId, vypiskaBtn.x(), vypiskaBtn.y());
|
||
QThread::msleep(3000);
|
||
|
||
// Сохраняем дамп Выписки для анализа формата
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
|
||
// Закрываем возможный диалог "Ошибка" на Выписке
|
||
if (!xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Ошибка")).isEmpty()) {
|
||
Node okBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("ОК"));
|
||
if (!okBtn.isEmpty()) {
|
||
qDebug() << "[Dushanbe::GetLastTransactions] Error dialog on Выписка, tapping OK";
|
||
AdbUtils::makeTap(m_deviceId, okBtn.x(), okBtn.y());
|
||
QThread::msleep(1000);
|
||
}
|
||
const int x = device.screenWidth / 2;
|
||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight / 4, x, device.screenHeight * 3 / 4);
|
||
QThread::msleep(3000);
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
}
|
||
|
||
// Находим accountId
|
||
int accountId = -1;
|
||
const QList<MaterialInfo> accounts = MaterialDAO::getAccounts(device.id, m_appCode);
|
||
if (!accounts.isEmpty()) {
|
||
accountId = accounts.first().id;
|
||
} else {
|
||
qWarning() << "[Dushanbe::GetLastTransactions] No account found";
|
||
}
|
||
|
||
qDebug() << "[Dushanbe::GetLastTransactions] Searching: amount=" << m_searchAmount
|
||
<< "time=" << m_searchTime.toString(Qt::ISODate);
|
||
|
||
// Время в банковском приложении показывается в таймзоне устройства
|
||
const QString tzName = AdbUtils::getDeviceTimezone(m_deviceId);
|
||
QTimeZone tjTz(tzName.toUtf8());
|
||
if (!tjTz.isValid()) tjTz = QTimeZone("Asia/Dushanbe");
|
||
const int maxScrolls = 10;
|
||
|
||
for (int scroll = 0; scroll <= maxScrolls; ++scroll) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
const QList<ScreenXmlParser::TransactionItem> items = xmlScreenParser.parseTransactionItems();
|
||
|
||
for (const auto &tx : items) {
|
||
if (qAbs(tx.amount - m_searchAmount) > 0.01) continue;
|
||
|
||
qDebug() << "[Dushanbe::GetLastTransactions] Amount match:" << tx.amount
|
||
<< "phone:" << tx.phone << "time:" << tx.time;
|
||
|
||
// Предварительная проверка времени из списка (±1 час)
|
||
if (m_searchTime.isValid() && !tx.time.isEmpty()) {
|
||
const QTime listTime = QTime::fromString(tx.time, "HH:mm:ss");
|
||
const QTime searchLocal = m_searchTime.toTimeZone(tjTz).time();
|
||
if (listTime.isValid() && searchLocal.isValid()) {
|
||
const int diffSecs = qAbs(listTime.secsTo(searchLocal));
|
||
if (diffSecs > 3600 && diffSecs < 82800) { // 82800 = 24h - 1h (для полуночи)
|
||
qDebug() << "[Dushanbe::GetLastTransactions] Time pre-filter skip:"
|
||
<< tx.time << "vs" << searchLocal.toString("HH:mm:ss")
|
||
<< "diff=" << diffSecs << "sec";
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Ищем соответствующий node чтобы по нему тапнуть
|
||
Node txNode;
|
||
for (const Node &node : xmlScreenParser.findAllNodes()) {
|
||
if (node.className != "android.widget.ImageView") continue;
|
||
if (!node.contentDesc.contains("***")) continue;
|
||
// Проверяем что содержит нужную сумму и время
|
||
const QString amountStr = QString::number(tx.amount, 'f', 2);
|
||
if (node.contentDesc.contains(amountStr) && node.contentDesc.contains(tx.time)) {
|
||
txNode = node;
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (txNode.x() == 0 && txNode.y() == 0) {
|
||
qWarning() << "[Dushanbe::GetLastTransactions] Cannot find node for transaction";
|
||
continue;
|
||
}
|
||
|
||
AdbUtils::makeTap(m_deviceId, txNode.x(), txNode.y());
|
||
QThread::msleep(3000);
|
||
|
||
// Ждём экран деталей — ищем кнопку "Назад" (признак экрана детали)
|
||
bool detailLoaded = false;
|
||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||
if (!xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Назад")).isEmpty() ||
|
||
!xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Поделиться")).isEmpty()) {
|
||
detailLoaded = true;
|
||
break;
|
||
}
|
||
QThread::msleep(1500);
|
||
}
|
||
|
||
if (!detailLoaded) {
|
||
qWarning() << "[Dushanbe::GetLastTransactions] Detail screen not loaded";
|
||
AdbUtils::goBack(m_deviceId);
|
||
QThread::msleep(1000);
|
||
continue;
|
||
}
|
||
|
||
// OCR скриншота деталей
|
||
const QByteArray detailScreenshot = AdbUtils::captureScreenshotBytes(m_deviceId);
|
||
const QString ocrText = recognizeTextFromImage(detailScreenshot);
|
||
qDebug() << "[Dushanbe::GetLastTransactions] OCR result:" << ocrText;
|
||
|
||
OcrTransactionDetail detail = parseDetailFromOcr(ocrText);
|
||
|
||
// Точечный OCR для номера операции (кроп + только цифры)
|
||
const QString preciseOpId = recognizeOperationId(detailScreenshot);
|
||
if (!preciseOpId.isEmpty()) {
|
||
detail.operationId = preciseOpId;
|
||
}
|
||
|
||
// Проверяем время ±1 час
|
||
if (m_searchTime.isValid() && !detail.date.isEmpty() && !detail.time.isEmpty()) {
|
||
QDateTime txTime = QDateTime::fromString(
|
||
detail.date + " " + detail.time, "dd.MM.yyyy HH:mm:ss");
|
||
if (!txTime.isValid()) {
|
||
txTime = QDateTime::fromString(detail.date + " " + detail.time, "dd.MM.yyyy HH:mm");
|
||
}
|
||
if (txTime.isValid()) {
|
||
txTime.setTimeZone(tjTz);
|
||
const qint64 diffSecs = qAbs(txTime.toUTC().secsTo(m_searchTime.toUTC()));
|
||
if (diffSecs > 3600) {
|
||
qDebug() << "[Dushanbe::GetLastTransactions] Time mismatch: diff=" << diffSecs << "sec, skipping";
|
||
AdbUtils::goBack(m_deviceId);
|
||
QThread::msleep(1000);
|
||
continue;
|
||
}
|
||
}
|
||
}
|
||
|
||
// Нашли! Сохраняем
|
||
qDebug() << "[Dushanbe::GetLastTransactions] FOUND:"
|
||
<< "amount=" << detail.amount
|
||
<< "date=" << detail.date << detail.time
|
||
<< "opId=" << detail.operationId
|
||
<< "receiver=" << detail.receiverAccount
|
||
<< "status=" << detail.status;
|
||
|
||
if (accountId != -1) {
|
||
TransactionInfo saveTx;
|
||
saveTx.accountId = accountId;
|
||
saveTx.amount = detail.amount;
|
||
saveTx.fee = detail.fee;
|
||
saveTx.bankName = m_appCode;
|
||
saveTx.bankTime = detail.date + " " + detail.time;
|
||
saveTx.bankTrExternalId = "dushanbe_" + detail.date + "_" + detail.time;
|
||
saveTx.description = detail.provider;
|
||
saveTx.phone = "";
|
||
saveTx.name = detail.receiverAccount;
|
||
saveTx.status = detail.status.contains(QString::fromUtf8("Успешн"))
|
||
? TransactionStatus::Complete : TransactionStatus::Unknown;
|
||
saveTx.type = TransactionType::Phone;
|
||
saveTx.timestamp = QDateTime::currentDateTimeUtc();
|
||
|
||
QDateTime txTime = QDateTime::fromString(
|
||
detail.date + " " + detail.time, "dd.MM.yyyy HH:mm:ss");
|
||
if (txTime.isValid()) {
|
||
txTime.setTimeZone(tjTz);
|
||
saveTx.completeTime = txTime;
|
||
}
|
||
|
||
TransactionDAO::insertTransactionIfNotExists(saveTx);
|
||
}
|
||
|
||
// Назад к истории
|
||
Node nazadBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Назад"));
|
||
if (nazadBtn.x() > 0 && nazadBtn.y() > 0) {
|
||
AdbUtils::makeTap(m_deviceId, nazadBtn.x(), nazadBtn.y());
|
||
} else {
|
||
AdbUtils::goBack(m_deviceId);
|
||
}
|
||
QThread::msleep(1000);
|
||
|
||
emit finishedWithResult(m_error); // m_error пустой — успех
|
||
return;
|
||
}
|
||
|
||
// Скроллим вниз
|
||
if (scroll < maxScrolls) {
|
||
const int x = device.screenWidth / 2;
|
||
const int fromY = device.screenHeight * 3 / 4;
|
||
const int toY = device.screenHeight / 4;
|
||
AdbUtils::makeSwipe(m_deviceId, x, fromY, x, toY);
|
||
QThread::msleep(2000);
|
||
}
|
||
}
|
||
|
||
m_error = "Transaction not found: amount=" + QString::number(m_searchAmount, 'f', 2)
|
||
+ " time=" + m_searchTime.toString(Qt::ISODate);
|
||
qWarning() << "[Dushanbe::GetLastTransactions]" << m_error;
|
||
|
||
// Возвращаемся на главную
|
||
Node glavnayaBtn = xmlScreenParser.findNodeByContentDesc(QString::fromUtf8("Главная"));
|
||
if (glavnayaBtn.x() > 0 && glavnayaBtn.y() > 0) {
|
||
AdbUtils::makeTap(m_deviceId, glavnayaBtn.x(), glavnayaBtn.y());
|
||
} else {
|
||
AdbUtils::goBack(m_deviceId);
|
||
}
|
||
QThread::msleep(1000);
|
||
|
||
emit finishedWithResult(m_error);
|
||
}
|
||
|
||
} // namespace Dushanbe
|