Add Ozon pay_to_new_number and pay_to_old_number XML assets
Included XML files for the "pay to new number" and "pay to old number" features in the Ozon platform.
@ -245,11 +245,16 @@ bool AdbUtils::enableAdbIMEKeyboard(const QString &deviceId) {
|
||||
|
||||
bool AdbUtils::inputAdbIMEText(const QString &deviceId, const QString &text) {
|
||||
QProcess process;
|
||||
QString command = QString(
|
||||
"%1 -s %2 shell am broadcast -a ADB_INPUT_TEXT --es msg '%3'"
|
||||
).arg(adbPath(), deviceId, text);
|
||||
process.start(command);
|
||||
process.start(adbPath(), {
|
||||
"-s", deviceId,
|
||||
"shell", "am", "broadcast",
|
||||
"-a", "ADB_INPUT_TEXT",
|
||||
"--es", "msg", text
|
||||
});
|
||||
process.waitForFinished();
|
||||
qDebug() << "[AdbUtils] inputAdbIMEText result:" << process.exitCode()
|
||||
<< "output:" << process.readAllStandardOutput().trimmed()
|
||||
<< "error:" << process.readAllStandardError().trimmed();
|
||||
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||
}
|
||||
|
||||
|
||||
@ -102,6 +102,13 @@ bool CommonScript::runAppAndGoToHomeScreen(
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверяем на баннер Google Play "Доступно обновление"
|
||||
if (Node gpNode = xmlScreenParser.tryToFindGooglePlayBanner(); !gpNode.isEmpty()) {
|
||||
qDebug() << "[runAppAndGoToHomeScreen] Google Play update banner detected, closing...";
|
||||
AdbUtils::makeTap(deviceId, gpNode.x(), gpNode.y());
|
||||
QThread::msleep(1500);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Проверяем, что это экран ввода пин-кода
|
||||
if (!findAndInputPincode) {
|
||||
|
||||
@ -256,6 +256,16 @@ void GetCardInfoScript::doStart() {
|
||||
qDebug() << "[GetCardInfo] Card number found on attempt" << attempt + 1;
|
||||
break;
|
||||
}
|
||||
|
||||
// Закрываем баннер "Понятно" если он перекрывает экран
|
||||
Node bannerBtn = xmlScreenParser.findTextNode("Понятно");
|
||||
if (!bannerBtn.isEmpty()) {
|
||||
qDebug() << "[GetCardInfo] Banner 'Понятно' detected, closing...";
|
||||
AdbUtils::makeTap(m_deviceId, bannerBtn.x(), bannerBtn.y());
|
||||
QThread::msleep(1000);
|
||||
continue;
|
||||
}
|
||||
|
||||
qDebug() << "[GetCardInfo] Card number not visible, attempt" << attempt + 1 << "/ 10";
|
||||
}
|
||||
|
||||
|
||||
@ -1,10 +1,15 @@
|
||||
#include "GetLastTransactionsScript.h"
|
||||
|
||||
#include <QProcess>
|
||||
#include <QThread>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "time/DateUtils.h"
|
||||
|
||||
namespace Ozon {
|
||||
|
||||
@ -61,13 +66,29 @@ void GetLastTransactionsScript::doStart() {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
|
||||
// Скроллим вниз до кнопки "Все" и тапаем по ней
|
||||
Node allBtn = swipeToButton(m_deviceId, "Все", m_counter,
|
||||
device.screenWidth, device.screenHeight);
|
||||
// Кнопка "Все" на домашнем экране находится у границы навбара (y ≈ 2190),
|
||||
// поэтому сначала скроллим вниз, чтобы она поднялась выше навигационной панели
|
||||
const int navBarTop = device.screenHeight - (device.screenHeight / 10); // ~90% высоты экрана
|
||||
Node allBtn;
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
// Сначала скроллим, потом ищем
|
||||
const int x = device.screenWidth / 2;
|
||||
const int offset = device.screenHeight / 4;
|
||||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight - offset, x, offset);
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
|
||||
allBtn = xmlScreenParser.findButtonNode("Все", false);
|
||||
if (!allBtn.isEmpty() && allBtn.y() > 0 && allBtn.y() < navBarTop) {
|
||||
break;
|
||||
}
|
||||
allBtn = {};
|
||||
qDebug() << "[GetLastTransactions] 'Все' not found above navbar, attempt" << i + 1 << "/ 10";
|
||||
}
|
||||
|
||||
if (allBtn.isEmpty()) {
|
||||
m_error = "Button 'Все' not found after scrolling: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
@ -76,16 +97,206 @@ void GetLastTransactionsScript::doStart() {
|
||||
AdbUtils::makeTap(m_deviceId, allBtn.x(), allBtn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Парсим транзакции за сегодня и вчера
|
||||
// Ждём загрузки экрана "Операции"
|
||||
bool loaded = false;
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
QList<TransactionInfo> transactions = xmlScreenParser.parseTodayAndYesterdayHistory();
|
||||
|
||||
qDebug() << "[GetLastTransactions] Found" << transactions.size() << "transactions";
|
||||
for (const TransactionInfo &tx : transactions) {
|
||||
qDebug() << "[GetLastTransactions]" << tx.description << tx.amount << tx.bankTime;
|
||||
if (xmlScreenParser.isTransactionListScreen()) {
|
||||
loaded = true;
|
||||
break;
|
||||
}
|
||||
qDebug() << "[GetLastTransactions] Waiting for transaction list screen, attempt" << attempt + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (!loaded) {
|
||||
m_error = "Transaction list screen not loaded: " + m_deviceId;
|
||||
qWarning() << m_error;
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ждём появления транзакций (могут подгружаться)
|
||||
QList<Node> txButtons;
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||
if (!txButtons.isEmpty()) break;
|
||||
qDebug() << "[GetLastTransactions] No transactions yet, attempt" << attempt + 1 << "/ 5";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
qDebug() << "[GetLastTransactions] Found" << txButtons.size() << "transaction buttons";
|
||||
|
||||
// Находим accountId для сохранения транзакций
|
||||
int accountId = -1;
|
||||
const QList<AccountInfo> accounts = AccountDAO::getAccounts(m_deviceId, m_appCode);
|
||||
if (!accounts.isEmpty()) {
|
||||
accountId = accounts.first().id;
|
||||
qDebug() << "[GetLastTransactions] Using accountId:" << accountId;
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] No account found for" << m_deviceId << m_appCode;
|
||||
}
|
||||
|
||||
// Для каждой транзакции: клик → берём дату из деталей → назад
|
||||
QList<TransactionInfo> transactions;
|
||||
for (int i = 0; i < txButtons.size(); ++i) {
|
||||
const Node &btn = txButtons[i];
|
||||
|
||||
// Если кнопка внизу экрана (за навбаром) — скроллим до неё
|
||||
const int navBarTop = device.screenHeight - (device.screenHeight / 10);
|
||||
if (btn.y() <= 0 || btn.y() >= navBarTop) {
|
||||
qDebug() << "[GetLastTransactions] Transaction" << i + 1 << "is off-screen (y=" << btn.y() << "), scrolling...";
|
||||
for (int s = 0; s < 5; ++s) {
|
||||
const int x = device.screenWidth / 2;
|
||||
const int offset = device.screenHeight / 4;
|
||||
AdbUtils::makeSwipe(m_deviceId, x, device.screenHeight - offset, x, offset);
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||
if (i < txButtons.size() && txButtons[i].y() > 0 && txButtons[i].y() < navBarTop) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i >= txButtons.size()) {
|
||||
qWarning() << "[GetLastTransactions] Transaction" << i + 1 << "not found after scrolling";
|
||||
break;
|
||||
}
|
||||
}
|
||||
const Node &visibleBtn = txButtons[i];
|
||||
TransactionInfo tx = xmlScreenParser.parseTransactionButtonText(visibleBtn.text);
|
||||
|
||||
qDebug() << "[GetLastTransactions] Tapping transaction" << i + 1 << ":" << visibleBtn.text.left(50);
|
||||
AdbUtils::makeTap(m_deviceId, visibleBtn.x(), visibleBtn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Ждём загрузки деталей — ищем дату в шапке
|
||||
TransactionInfo detail;
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
detail = xmlScreenParser.parseTransactionDetail();
|
||||
if (!detail.bankTime.isEmpty()) break;
|
||||
qDebug() << "[GetLastTransactions] Detail not loaded, attempt" << attempt + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
tx.bankName = "ozon";
|
||||
tx.bankTime = detail.bankTime;
|
||||
tx.status = TransactionStatus::Complete;
|
||||
tx.timestamp = DateUtils::getUtcNow();
|
||||
if (!detail.name.isEmpty()) tx.name = detail.name;
|
||||
if (!detail.phone.isEmpty()) {
|
||||
// Оставляем только цифры
|
||||
QString cleanPhone;
|
||||
for (const QChar &ch : detail.phone) {
|
||||
if (ch.isDigit()) cleanPhone.append(ch);
|
||||
}
|
||||
tx.phone = cleanPhone;
|
||||
tx.type = TransactionType::Phone;
|
||||
}
|
||||
|
||||
// Чистим bankTime от unicode символов: заменяем все не-ASCII пробелы на обычные
|
||||
if (!tx.bankTime.isEmpty()) {
|
||||
QString dtStr = tx.bankTime;
|
||||
for (int ci = 0; ci < dtStr.size(); ++ci) {
|
||||
const QChar ch = dtStr.at(ci);
|
||||
if (ch != ' ' && ch.category() == QChar::Separator_Space) {
|
||||
dtStr[ci] = ' ';
|
||||
}
|
||||
}
|
||||
tx.bankTime = dtStr;
|
||||
const QTimeZone msk("Europe/Moscow");
|
||||
QDateTime parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy, HH:mm");
|
||||
if (!parsed.isValid()) {
|
||||
parsed = QDateTime::fromString(dtStr, "dd.MM.yyyy,HH:mm");
|
||||
}
|
||||
if (parsed.isValid()) {
|
||||
parsed.setTimeZone(msk);
|
||||
tx.completeTime = parsed;
|
||||
}
|
||||
}
|
||||
|
||||
qDebug() << "[GetLastTransactions]"
|
||||
<< "date:" << tx.bankTime
|
||||
<< "name:" << tx.description
|
||||
<< "sender:" << tx.name
|
||||
<< "phone:" << tx.phone
|
||||
<< "amount:" << (tx.amount >= 0 ? "+" : "") + QString::number(tx.amount, 'f', 2);
|
||||
|
||||
// Сохраняем в БД если не существует
|
||||
// Берём первый аккаунт для этого устройства и приложения
|
||||
if (accountId != -1) {
|
||||
tx.accountId = accountId;
|
||||
if (TransactionDAO::insertTransactionIfNotExists(tx)) {
|
||||
qDebug() << "[GetLastTransactions] Saved transaction to DB";
|
||||
|
||||
// Делаем скриншот документа транзакции
|
||||
Node docBtn = xmlScreenParser.findButtonNode("Документы", false);
|
||||
if (!docBtn.isEmpty()) {
|
||||
qDebug() << "[GetLastTransactions] Tapping 'Документы'...";
|
||||
AdbUtils::makeTap(m_deviceId, docBtn.x(), docBtn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Ждём появления кнопки "Поделиться"
|
||||
bool docLoaded = false;
|
||||
for (int da = 0; da < 10; ++da) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
Node shareBtn = xmlScreenParser.findNodeByContentDesc("Поделиться");
|
||||
if (!shareBtn.isEmpty()) {
|
||||
docLoaded = true;
|
||||
break;
|
||||
}
|
||||
qDebug() << "[GetLastTransactions] Waiting for 'Поделиться', attempt" << da + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (docLoaded) {
|
||||
QThread::msleep(500);
|
||||
QProcess screencapProc;
|
||||
screencapProc.start("adb", QStringList() << "-s" << m_deviceId << "exec-out" << "screencap" << "-p");
|
||||
screencapProc.waitForFinished(5000);
|
||||
const QByteArray screenshot = screencapProc.readAllStandardOutput();
|
||||
if (!screenshot.isEmpty()) {
|
||||
// Сохраняем в БД
|
||||
TransactionInfo saved = TransactionDAO::findByBankTimeAndAmount(
|
||||
accountId, tx.bankName, tx.bankTime, tx.amount);
|
||||
if (saved.id != -1) {
|
||||
TransactionDAO::updateReceiptImage(saved.id, screenshot);
|
||||
qDebug() << "[GetLastTransactions] Receipt saved to DB, txId:" << saved.id;
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] Screenshot is empty";
|
||||
}
|
||||
} else {
|
||||
qWarning() << "[GetLastTransactions] 'Поделиться' not found, skipping receipt";
|
||||
}
|
||||
|
||||
// Возвращаемся из документов в детали транзакции
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
} else {
|
||||
qDebug() << "[GetLastTransactions] Transaction already exists or error";
|
||||
}
|
||||
}
|
||||
|
||||
transactions.append(tx);
|
||||
|
||||
// Возвращаемся в список
|
||||
AdbUtils::goBack(m_deviceId);
|
||||
QThread::msleep(1500);
|
||||
|
||||
// Ждём возврата на экран списка
|
||||
for (int attempt = 0; attempt < 5; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
if (xmlScreenParser.isTransactionListScreen()) break;
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
|
||||
// Обновляем кнопки (после возврата координаты могут измениться)
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_deviceId, ++m_counter));
|
||||
txButtons = xmlScreenParser.findTransactionButtons(8);
|
||||
}
|
||||
|
||||
AdbUtils::tryToKillApplication(m_deviceId, app.package);
|
||||
emit finishedWithResult(m_error);
|
||||
}
|
||||
|
||||
|
||||
@ -15,7 +15,8 @@ LoginAndCheckAccountsScript::LoginAndCheckAccountsScript(
|
||||
QString appCode,
|
||||
QObject *parent
|
||||
) : CommonScript(parent),
|
||||
m_deviceId(std::move(deviceId)), m_appCode(std::move(appCode)) {
|
||||
m_deviceId(std::move(deviceId)),
|
||||
m_appCode(std::move(appCode)) {
|
||||
}
|
||||
|
||||
LoginAndCheckAccountsScript::~LoginAndCheckAccountsScript() = default;
|
||||
|
||||
@ -1,17 +1,15 @@
|
||||
#include "PayByPhoneScript.h"
|
||||
|
||||
#include <QRandomGenerator>
|
||||
#include <QProcess>
|
||||
#include <QRegularExpression>
|
||||
#include <QThread>
|
||||
#include <utility>
|
||||
|
||||
#include "DatabaseManager.h"
|
||||
#include "adb/AdbUtils.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "dao/TransactionDAO.h"
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "db/DeviceInfo.h"
|
||||
|
||||
namespace Ozon {
|
||||
|
||||
@ -28,313 +26,310 @@ PayByPhoneScript::PayByPhoneScript(
|
||||
|
||||
PayByPhoneScript::~PayByPhoneScript() = default;
|
||||
|
||||
|
||||
void PayByPhoneScript::makePayment(
|
||||
const QString &deviceId,
|
||||
const QString &pinCode,
|
||||
int width,
|
||||
int height
|
||||
) {
|
||||
// Оплата со страницы карты
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node payBtn = xmlScreenParser.findButtonNode("Оплатить", false);
|
||||
if (!payBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, payBtn.x(), payBtn.y());
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_pay_btn_not_found");
|
||||
m_error = "Кнопка 'Оплатить' не нашлась";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
// TODO - По номеру карты В РСХБ и другие банки
|
||||
Node payTitle = xmlScreenParser.findTextNode("Платежи и переводы");
|
||||
if (!payTitle.isEmpty() && payTitle.y() < 300) {
|
||||
Node payByPhoneBtn = xmlScreenParser.findButtonNode("По номеру телефона В РСХБ и через", false);
|
||||
if (!payByPhoneBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, payByPhoneBtn.x(), payByPhoneBtn.y());
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_not_found_pay_by_phone");
|
||||
m_error = "Не смогли найти пункт 'По номеру телефона В РСХБ и через'";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_go_to_payments");
|
||||
m_error = "Не смогли перейти на экран 'Платежи и переводы'";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node payByPhoneTitle = xmlScreenParser.findTextNode("Перевод по телефону");
|
||||
Node continueBtn = xmlScreenParser.findButtonNode("Продолжить", false);
|
||||
if (!payByPhoneTitle.isEmpty() && payByPhoneTitle.y() < 300 && continueBtn.isEmpty()) {
|
||||
Node inputPhone = xmlScreenParser.findFirstEditText();
|
||||
AdbUtils::makeTap(deviceId, inputPhone.x(), inputPhone.y());
|
||||
QThread::msleep(500);
|
||||
AdbUtils::inputText(deviceId, m_phone);
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_find_pay_by_phone");
|
||||
m_error = "Не смогли найти пункт 'Перевод по телефону'";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
QString payBankName = m_bankName == QStringLiteral("Россельхозбанк") ? "Россельхозбанк" : "Другой банк через СБП";
|
||||
Node payOtherBankBtn = xmlScreenParser.findButtonNode(payBankName, false);
|
||||
if (!payOtherBankBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, payOtherBankBtn.x(), payOtherBankBtn.y());
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_tap_on_bank");
|
||||
m_error = "Не смогли нажать на первичный выбор банка";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_bankName != QStringLiteral("Россельхозбанк")) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Node allBanksTitle = xmlScreenParser.findTextNode("Банки участники");
|
||||
if (!allBanksTitle.isEmpty() && allBanksTitle.y() < 300) {
|
||||
Node inputBankName = xmlScreenParser.findFirstEditText();
|
||||
AdbUtils::makeTap(deviceId, inputBankName.x(), inputBankName.y());
|
||||
QThread::msleep(500);
|
||||
AdbUtils::enableAdbIMEKeyboard(deviceId);
|
||||
AdbUtils::inputAdbIMEText(deviceId, m_bankName);
|
||||
AdbUtils::disableAdbIMEKeyboard(deviceId);
|
||||
QThread::msleep(1000);
|
||||
break;
|
||||
} else {
|
||||
qDebug() << "--- allBanksTitle not found, i: " << i;
|
||||
}
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Node bankNameListItem = xmlScreenParser.findTextNode(m_bankName);
|
||||
if (!bankNameListItem.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, bankNameListItem.x(), bankNameListItem.y());
|
||||
QThread::msleep(1000);
|
||||
break;
|
||||
} else {
|
||||
qDebug() << "--- bankNameListItem not found, i: " << i;
|
||||
}
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
|
||||
const bool isRshbBank = m_bankName == QStringLiteral("Россельхозбанк");
|
||||
QString payByPhoneScreenTitleText = isRshbBank ? "Перевод клиенту РСХБ" : "Перевод по телефону";
|
||||
Node payByPhoneScreenTitle = xmlScreenParser.findTextNode(payByPhoneScreenTitleText);
|
||||
Node continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false);
|
||||
|
||||
if (isRshbBank ||
|
||||
!payByPhoneScreenTitle.isEmpty() && payByPhoneScreenTitle.y() < 300 && !continuePayBtn.isEmpty()) {
|
||||
Node sumBtn = xmlScreenParser.findButtonNode("0", false);
|
||||
if (isRshbBank || !sumBtn.isEmpty()) {
|
||||
if (!isRshbBank) {
|
||||
AdbUtils::makeTap(deviceId, sumBtn.x(), sumBtn.y());
|
||||
QThread::msleep(500);
|
||||
}
|
||||
AdbUtils::inputText(deviceId, QString::number(m_amount));
|
||||
QThread::msleep(500);
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node readyBtn = xmlScreenParser.findTextNode("Готово");
|
||||
if (!readyBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, readyBtn.x(), readyBtn.y());
|
||||
QThread::msleep(500);
|
||||
if (isRshbBank) {
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false);
|
||||
}
|
||||
AdbUtils::makeTap(deviceId, continuePayBtn.x(), continuePayBtn.y());
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_tap_on_ready");
|
||||
m_error = "Не смогли нажать 'Готово'";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_tap_on_continue");
|
||||
m_error = "Не смогли нажать 'Продолжить'";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_find_bank_title");
|
||||
m_error = "Не смогли найти заголовок: " + payByPhoneScreenTitleText;
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
bool inputedPinCode = false;
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
// TODO кнопка содержит "Перевести 10 ₽" если будет от 1000 то скорее всвего будет "Перевести 1 000 ₽",
|
||||
// TODO не ясно как форматирование будет, поэтому пока на начало зашьем
|
||||
Node makePayBtn = xmlScreenParser.findButtonNode("Перевести ", true);
|
||||
if (!makePayBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, makePayBtn.x(), makePayBtn.y());
|
||||
QThread::msleep(500);
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
if (const QList<Node> pincode = xmlScreenParser.tryToFindPinCode(pinCode, "Введите код доступа");
|
||||
pincode.size() == 4) {
|
||||
inputPinCode(deviceId, pincode);
|
||||
inputedPinCode = true;
|
||||
// После ввода пин-кода, ждем 3с
|
||||
QThread::msleep(3000);
|
||||
}
|
||||
break;
|
||||
} else {
|
||||
qWarning() << "--- makePayBtn not found, i: " << i;
|
||||
}
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
|
||||
if (!isRshbBank && !inputedPinCode) {
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_cant_input_pin_code");
|
||||
m_error = "Не смогли ввести пин-код!'";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
|
||||
for (int i = 0; i < 10; ++i) {
|
||||
Node moreDetailBtn = xmlScreenParser.findButtonNode("Детали операции", false);
|
||||
if (!moreDetailBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, moreDetailBtn.x(), moreDetailBtn.y());
|
||||
QThread::msleep(200);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
|
||||
// TODO считываем все данные по переводу
|
||||
TransactionInfo transaction = xmlScreenParser.parseTransactionInfo(width, height);
|
||||
if (isRshbBank) {
|
||||
transaction.bankName = m_bankName;
|
||||
}
|
||||
transaction.accountId = m_account.id;
|
||||
transaction.amount = -m_amount;
|
||||
transaction.type = TransactionType::Phone;
|
||||
transaction.id = TransactionDAO::insertTransaction(transaction);
|
||||
if (transaction.id == -1) {
|
||||
// FIXME alert!!
|
||||
qDebug() << "Transaction has not inserted...";
|
||||
}
|
||||
|
||||
// ждем 10 секунд смену статуса
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
Node paymentCompleted = xmlScreenParser.findTextNode("Исполнен", 0, 0, width, height / 2);
|
||||
if (!paymentCompleted.isEmpty()) {
|
||||
transaction.status = TransactionStatus::Complete;
|
||||
if (!TransactionDAO::updateTransactionStatus(transaction.id, TransactionStatus::Complete)) {
|
||||
qDebug() << "Not updated: " << transaction.id;
|
||||
}
|
||||
// TODO обновляем данные
|
||||
break;
|
||||
}
|
||||
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
|
||||
postNewTransactionData(m_account, transaction);
|
||||
|
||||
AdbUtils::takeScreenshot(deviceId, "pay_by_phone_finish_transactions_" + transaction.id);
|
||||
AdbUtils::takeXmlDump(deviceId, "pay_by_phone_finish_transactions_" + transaction.id);
|
||||
break;
|
||||
} else {
|
||||
qWarning() << "--- moreDetailBtn not found, i: " << i;
|
||||
}
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
|
||||
Node homeBtn = xmlScreenParser.findButtonNode("Вернуться на главную", false);
|
||||
AdbUtils::makeTap(deviceId, homeBtn.x(), homeBtn.y());
|
||||
QThread::msleep(1000);
|
||||
}
|
||||
|
||||
void PayByPhoneScript::doStart() {
|
||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appCode);
|
||||
|
||||
if (device.id.isEmpty() || app.id == -1) {
|
||||
m_error = "Device or app not found....";
|
||||
qDebug() << m_error;
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter));
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Не смогли дойти до домашней страницы";
|
||||
qDebug() << m_error;
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) {
|
||||
QList<QPair<AccountInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
|
||||
for (auto &[account, node]: accounts) {
|
||||
account.deviceId = m_account.deviceId;
|
||||
account.appCode = m_account.appCode;
|
||||
account.status = account.amount > 0 ? AccountStatus::Active : AccountStatus::Off;
|
||||
if (!AccountDAO::upsertAccount(account)) {
|
||||
qDebug() << "Not updated: " << account.lastNumbers;
|
||||
}
|
||||
}
|
||||
|
||||
postAccountsData(accounts);
|
||||
|
||||
for (auto &[account, node]: accounts) {
|
||||
// FIXME будем считать что числа последние везде уникальные
|
||||
if (m_account.lastNumbers == account.lastNumbers) {
|
||||
if (account.amount >= m_amount) {
|
||||
AdbUtils::makeTap(device.id, node.x(), node.y());
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(device.id, "pay_by_phone_not_enough_money_" + QString::number(m_account.id));
|
||||
m_error = "Not enough money, id: " + QString::number(m_account.id)
|
||||
+ ", amount: " + QString::number(account.amount)
|
||||
+ ", trx amount: " + QString::number(m_amount);
|
||||
m_error = "Device or app not found: " + m_account.deviceId;
|
||||
qCritical() << m_error;
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
break;
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||||
|
||||
// Закрываем рекламный bottom sheet
|
||||
if (Node adNode = xmlScreenParser.tryToFindAdBanner(); !adNode.isEmpty()) {
|
||||
qDebug() << "[PayByPhone] Ad banner detected, closing...";
|
||||
AdbUtils::makeTap(device.id, adNode.x(), adNode.y());
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||||
}
|
||||
|
||||
// Если не на домашнем экране — перезапускаем
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
qDebug() << "[PayByPhone] Not on home screen, restarting app...";
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode,
|
||||
device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Cannot reach home screen: " + device.id;
|
||||
qWarning() << m_error;
|
||||
AdbUtils::tryToKillApplication(device.id, app.package);
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||||
}
|
||||
|
||||
// Закрываем баннеры
|
||||
tryToCloseAllBannersOnHomePage(device.id, device.screenWidth, device.screenHeight);
|
||||
QThread::msleep(500);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||||
|
||||
// Закрываем баннер Google Play "Доступно обновление"
|
||||
if (Node gpNode = xmlScreenParser.tryToFindGooglePlayBanner(); !gpNode.isEmpty()) {
|
||||
qDebug() << "[PayByPhone] Google Play update banner detected, closing...";
|
||||
AdbUtils::makeTap(device.id, gpNode.x(), gpNode.y());
|
||||
QThread::msleep(1500);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||||
}
|
||||
|
||||
// Закрываем баннер "Заказать карту" — тапаем сверху экрана
|
||||
if (Node orderCardNode = xmlScreenParser.findTextNode("Заказать карту"); !orderCardNode.isEmpty()) {
|
||||
qDebug() << "[PayByPhone] 'Заказать карту' banner detected, closing...";
|
||||
AdbUtils::makeTap(device.id, device.screenWidth / 2, 100);
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(device.id, ++m_counter));
|
||||
}
|
||||
|
||||
makePayment(device.id, app.pinCode, device.screenWidth, device.screenHeight);
|
||||
// AdbUtils::tryToKillApplication(device.id, app.package); TODO - надо ли это???
|
||||
} else {
|
||||
AdbUtils::takeScreenshot(device.id, "pay_by_phone_cant_open_all_products_" + QString::number(m_account.id));
|
||||
AdbUtils::tryToKillApplication(device.id, app.package);
|
||||
m_error = "Не смогли открыть 'Все продукты'";
|
||||
qDebug() << m_error;
|
||||
}
|
||||
|
||||
emit finishedWithResult(m_error);
|
||||
}
|
||||
|
||||
void PayByPhoneScript::makePayment(
|
||||
const QString &deviceId,
|
||||
const QString &pinCode,
|
||||
const int width,
|
||||
const int height
|
||||
) {
|
||||
// 1. Нажимаем "Перевести" на домашнем экране
|
||||
Node transferBtn = xmlScreenParser.findButtonNode("Перевести", false);
|
||||
if (transferBtn.isEmpty()) {
|
||||
m_error = "Кнопка 'Перевести' не найдена на домашнем экране";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "[PayByPhone] Tapping 'Перевести'...";
|
||||
AdbUtils::makeTap(deviceId, transferBtn.x(), transferBtn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// 2. Ждём экран "Платежи и переводы" с полем "Телефон получателя"
|
||||
bool paymentsPageLoaded = false;
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node phoneField = xmlScreenParser.findFirstEditText();
|
||||
if (!phoneField.isEmpty()) {
|
||||
paymentsPageLoaded = true;
|
||||
break;
|
||||
}
|
||||
qDebug() << "[PayByPhone] Waiting for payments page, attempt" << attempt + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (!paymentsPageLoaded) {
|
||||
m_error = "Экран 'Платежи и переводы' не загрузился";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Тапаем на поле "Телефон получателя" чтобы перейти на экран "Кому перевести"
|
||||
Node phoneFieldTap = xmlScreenParser.findFirstEditText();
|
||||
qDebug() << "[PayByPhone] Tapping 'Телефон получателя'...";
|
||||
AdbUtils::makeTap(deviceId, phoneFieldTap.x(), phoneFieldTap.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// 4. Ждём экран "Кому перевести"
|
||||
bool phoneScreenLoaded = false;
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node title = xmlScreenParser.findTextNode("Кому перевести");
|
||||
if (!title.isEmpty()) {
|
||||
phoneScreenLoaded = true;
|
||||
break;
|
||||
}
|
||||
qDebug() << "[PayByPhone] Waiting for 'Кому перевести', attempt" << attempt + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (!phoneScreenLoaded) {
|
||||
m_error = "Экран 'Кому перевести' не загрузился";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
// 5. Тапаем на EditText и вводим номер телефона
|
||||
Node phoneInput;
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
phoneInput = xmlScreenParser.findFirstEditText();
|
||||
if (!phoneInput.isEmpty()) break;
|
||||
qDebug() << "[PayByPhone] Waiting for phone input field, attempt" << attempt + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
if (phoneInput.isEmpty()) {
|
||||
m_error = "Поле ввода телефона не найдено";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
AdbUtils::makeTap(deviceId, phoneInput.x(), phoneInput.y());
|
||||
QThread::msleep(500);
|
||||
AdbUtils::inputText(deviceId, m_phone);
|
||||
QThread::msleep(2000);
|
||||
|
||||
// 6. Ждём экран "По номеру телефона", обрабатывая промежуточные экраны
|
||||
bool sumScreenLoaded = false;
|
||||
bool bankSelected = false;
|
||||
bool otherBankClicked = false;
|
||||
for (int attempt = 0; attempt < 20; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
|
||||
// Проверяем: уже на экране суммы?
|
||||
Node sumTitle = xmlScreenParser.findTextNode("По номеру телефона");
|
||||
if (!sumTitle.isEmpty() && sumTitle.y() < 300) {
|
||||
sumScreenLoaded = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Ищем кнопку с номером телефона (новый перевод / последние переводы)
|
||||
QString phoneDigits = m_phone;
|
||||
phoneDigits.remove(QRegularExpression("[^0-9]"));
|
||||
bool foundPhoneBtn = false;
|
||||
for (const Node &btn : xmlScreenParser.findAllButtons()) {
|
||||
QString btnDigits = btn.text;
|
||||
btnDigits.remove(QRegularExpression("[^0-9]"));
|
||||
if (btnDigits.contains(phoneDigits) || phoneDigits.contains(btnDigits.right(10))) {
|
||||
qDebug() << "[PayByPhone] Found button with phone number:" << btn.text;
|
||||
AdbUtils::makeTap(deviceId, btn.x(), btn.y());
|
||||
QThread::msleep(2000);
|
||||
foundPhoneBtn = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (foundPhoneBtn) continue;
|
||||
|
||||
// Экран выбора банка: ищем "Другой банк" (текст) или EditText с hint "Название банка"
|
||||
if (!bankSelected) {
|
||||
// Вариант 1: кнопка "Другой банк" (content-desc="other-bank-item", text пустой)
|
||||
bool foundOtherBank = false;
|
||||
{
|
||||
Node node;
|
||||
for (const Node &n : xmlScreenParser.findAllNodes()) {
|
||||
if (n.contentDesc.contains("other-bank-item")) {
|
||||
node = n;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!node.isEmpty()) {
|
||||
qDebug() << "[PayByPhone] Tapping 'Другой банк' (other-bank-item)...";
|
||||
AdbUtils::makeTap(deviceId, node.x(), node.y());
|
||||
QThread::msleep(2000);
|
||||
foundOtherBank = true;
|
||||
otherBankClicked = true;
|
||||
}
|
||||
}
|
||||
if (foundOtherBank) continue;
|
||||
|
||||
// Вариант 2: уже на экране поиска банка (EditText) — только после клика на "Другой банк"
|
||||
if (otherBankClicked) {
|
||||
Node bankInput = xmlScreenParser.findFirstEditText();
|
||||
if (!bankInput.isEmpty()) {
|
||||
qDebug() << "[PayByPhone] Bank search screen detected, entering bank name...";
|
||||
AdbUtils::makeTap(deviceId, bankInput.x(), bankInput.y());
|
||||
QThread::msleep(500);
|
||||
|
||||
// Ввод кириллицы через ADB Keyboard
|
||||
AdbUtils::enableAdbIMEKeyboard(deviceId);
|
||||
QThread::msleep(500);
|
||||
AdbUtils::makeTap(deviceId, bankInput.x(), bankInput.y());
|
||||
QThread::msleep(500);
|
||||
AdbUtils::inputAdbIMEText(deviceId, m_bankName);
|
||||
QThread::msleep(2000);
|
||||
AdbUtils::disableAdbIMEKeyboard(deviceId);
|
||||
qDebug() << "[PayByPhone] Entered bank name via ADB IME:" << m_bankName;
|
||||
QThread::msleep(2000);
|
||||
|
||||
// Ищем банк в списке и кликаем по нему (кнопка с числовым content-desc)
|
||||
bool bankFound = false;
|
||||
for (int ba = 0; ba < 10; ++ba) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
for (const Node &node : xmlScreenParser.findAllNodes()) {
|
||||
if (node.className == "android.widget.Button"
|
||||
&& !node.contentDesc.isEmpty()
|
||||
&& node.contentDesc != "base-go-back-button"
|
||||
&& node.contentDesc != "native-toolbar-close-button"
|
||||
&& node.contentDesc != "clear-input-icon") {
|
||||
qDebug() << "[PayByPhone] Found bank in list, tapping:" << node.contentDesc << "bounds:" << node.x() << node.y();
|
||||
AdbUtils::makeTap(deviceId, node.x(), node.y());
|
||||
bankFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bankFound) break;
|
||||
qDebug() << "[PayByPhone] Waiting for bank list, attempt" << ba + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (!bankFound) {
|
||||
m_error = "Банк '" + m_bankName + "' не найден в списке";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
bankSelected = true;
|
||||
QThread::msleep(2000);
|
||||
continue;
|
||||
}
|
||||
} // otherBankClicked
|
||||
}
|
||||
|
||||
qDebug() << "[PayByPhone] Waiting for 'По номеру телефона', attempt" << attempt + 1 << "/ 20";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (!sumScreenLoaded) {
|
||||
m_error = "Экран ввода суммы не загрузился";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
// 7. Вводим сумму
|
||||
Node sumInput = xmlScreenParser.findFirstEditText();
|
||||
if (sumInput.isEmpty()) {
|
||||
m_error = "Поле ввода суммы не найдено";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
AdbUtils::makeTap(deviceId, sumInput.x(), sumInput.y());
|
||||
QThread::msleep(500);
|
||||
AdbUtils::inputText(deviceId, QString::number(static_cast<int>(m_amount)));
|
||||
QThread::msleep(1000);
|
||||
|
||||
// 6. Нажимаем "Продолжить"
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node continueBtn = xmlScreenParser.findButtonNode("Продолжить", false);
|
||||
if (continueBtn.isEmpty()) {
|
||||
m_error = "Кнопка 'Продолжить' не найдена";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
AdbUtils::makeTap(deviceId, continueBtn.x(), continueBtn.y());
|
||||
QThread::msleep(2000);
|
||||
|
||||
// 7. Ждём экран "Подтвердите перевод" с кнопкой "Перевести ..."
|
||||
bool confirmScreenLoaded = false;
|
||||
for (int attempt = 0; attempt < 10; ++attempt) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
Node confirmTitle = xmlScreenParser.findTextNode("Подтвердите перевод");
|
||||
if (!confirmTitle.isEmpty()) {
|
||||
confirmScreenLoaded = true;
|
||||
break;
|
||||
}
|
||||
qDebug() << "[PayByPhone] Waiting for 'Подтвердите перевод', attempt" << attempt + 1 << "/ 10";
|
||||
QThread::msleep(1500);
|
||||
}
|
||||
|
||||
if (!confirmScreenLoaded) {
|
||||
m_error = "Экран подтверждения не загрузился";
|
||||
qWarning() << "[PayByPhone]" << m_error;
|
||||
return;
|
||||
}
|
||||
|
||||
qDebug() << "[PayByPhone] Confirm screen loaded, stopping before confirmation";
|
||||
}
|
||||
|
||||
} // namespace Ozon
|
||||
@ -157,6 +157,20 @@ Node ScreenXmlParser::findButtonNode(const QString &text, const bool contains) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<Node> ScreenXmlParser::findAllButtons() {
|
||||
QList<Node> buttons;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className == "android.widget.Button" && !node.text.isEmpty()) {
|
||||
buttons.append(node);
|
||||
}
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
const QList<Node> &ScreenXmlParser::findAllNodes() const {
|
||||
return m_nodes;
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::findNodeByContentDesc(const QString &contentDesc) {
|
||||
for (const Node &node: m_nodes) {
|
||||
if (node.clickable && node.contentDesc.trimmed() == contentDesc) {
|
||||
@ -198,6 +212,25 @@ Node ScreenXmlParser::tryToFindAdBanner() {
|
||||
return {};
|
||||
}
|
||||
|
||||
Node ScreenXmlParser::tryToFindGooglePlayBanner() {
|
||||
// Баннер Google Play "Доступно обновление" — ищем кнопку закрытия по content-desc
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.contentDesc.contains("Закрыть диалоговое окно") && node.clickable) {
|
||||
return node;
|
||||
}
|
||||
}
|
||||
// Альтернативно — по content-desc "Доступно обновление"
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.contentDesc.contains("Доступно обновление")) {
|
||||
// Тапаем в верхнюю часть экрана (выше баннера), чтобы закрыть
|
||||
Node tapNode;
|
||||
tapNode.setBounds(540, 100, 541, 101);
|
||||
return tapNode;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ScreenXmlParser::isProfileScreen() {
|
||||
// Страница профиля Ozon: есть заголовок "Аккаунт" и кнопка "Выйти"
|
||||
bool hasTitle = false;
|
||||
@ -1223,6 +1256,110 @@ QString ScreenXmlParser::findFullCardNumber() {
|
||||
return {};
|
||||
}
|
||||
|
||||
bool ScreenXmlParser::isTransactionListScreen() {
|
||||
// Экран "Операции": WebView с text="Операции" + наличие кнопок фильтров
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className == "android.webkit.WebView" && node.text.trimmed() == "Операции") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QList<Node> ScreenXmlParser::findTransactionButtons(const int maxCount) {
|
||||
const QChar rubleSign(0x20BD); // ₽
|
||||
QList<Node> buttons;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className != "android.widget.Button") continue;
|
||||
const QString text = node.text.trimmed();
|
||||
if (!text.contains(rubleSign)) continue;
|
||||
// Пропускаем сводки "Расходы", "Доходы"
|
||||
if (text.startsWith("Расходы") || text.startsWith("Доходы")) continue;
|
||||
buttons.append(node);
|
||||
if (buttons.size() >= maxCount) break;
|
||||
}
|
||||
return buttons;
|
||||
}
|
||||
|
||||
TransactionInfo ScreenXmlParser::parseTransactionButtonText(const QString &text) {
|
||||
const QChar rubleSign(0x20BD); // ₽
|
||||
const QChar unicodeMinus(0x2212); // −
|
||||
const QChar nbsp(0x00A0); // non-breaking space
|
||||
|
||||
TransactionInfo info;
|
||||
|
||||
const int rubleIdx = text.indexOf(rubleSign);
|
||||
if (rubleIdx < 0) return info;
|
||||
|
||||
int signIdx = -1;
|
||||
bool isExpense = false;
|
||||
for (int i = rubleIdx - 1; i >= 0; --i) {
|
||||
const QChar ch = text.at(i);
|
||||
if (ch == '+') { signIdx = i; isExpense = false; break; }
|
||||
if (ch == '-' || ch == unicodeMinus) { signIdx = i; isExpense = true; break; }
|
||||
}
|
||||
if (signIdx < 0) return info;
|
||||
|
||||
info.description = text.left(signIdx).trimmed();
|
||||
|
||||
QString amountStr = text.mid(signIdx + 1, rubleIdx - signIdx - 1).trimmed();
|
||||
amountStr.remove(QChar(' '));
|
||||
amountStr.remove(nbsp);
|
||||
amountStr.replace(',', '.');
|
||||
info.amount = amountStr.toDouble();
|
||||
if (isExpense) info.amount = -info.amount;
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
TransactionInfo ScreenXmlParser::parseTransactionDetail() {
|
||||
TransactionInfo info;
|
||||
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className != "android.widget.TextView") continue;
|
||||
const QString text = node.text.trimmed();
|
||||
if (text.isEmpty()) continue;
|
||||
|
||||
// Дата+время: "15.01.2026, 15:55"
|
||||
if (info.bankTime.isEmpty() && text.contains('.') && text.contains(':') && text.length() <= 25) {
|
||||
info.bankTime = text;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Телефон: "+7 (985) 553-28-96"
|
||||
if (info.phone.isEmpty() && text.startsWith('+') && text.contains('(')) {
|
||||
info.phone = text;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
// Имя отправителя — TextView прямо перед телефоном (по y-координате чуть выше)
|
||||
if (!info.phone.isEmpty()) {
|
||||
// Ищем ноду телефона и берём ближайший TextView выше
|
||||
int phoneY = -1;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className == "android.widget.TextView" && node.text.trimmed() == info.phone) {
|
||||
phoneY = node.y();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (phoneY > 0) {
|
||||
int bestY = -1;
|
||||
for (const Node &node : m_nodes) {
|
||||
if (node.className != "android.widget.TextView") continue;
|
||||
const QString text = node.text.trimmed();
|
||||
if (text.isEmpty() || text == info.bankTime || text == info.phone) continue;
|
||||
if (node.y() > 0 && node.y() < phoneY && node.y() > bestY) {
|
||||
bestY = node.y();
|
||||
info.name = text;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return info;
|
||||
}
|
||||
|
||||
void ScreenXmlParser::test() {
|
||||
// TransactionInfo info2 = parseExpandedInfo(m_xml);
|
||||
// qDebug().noquote().nospace() << convertTransactionToString(info2);
|
||||
|
||||
@ -30,12 +30,18 @@ public:
|
||||
|
||||
Node findButtonNode(const QString &text, bool contains);
|
||||
|
||||
QList<Node> findAllButtons();
|
||||
|
||||
const QList<Node> &findAllNodes() const;
|
||||
|
||||
Node findNodeByContentDesc(const QString &contentDesc);
|
||||
|
||||
bool isHomeScreen();
|
||||
|
||||
Node tryToFindAdBanner();
|
||||
|
||||
Node tryToFindGooglePlayBanner();
|
||||
|
||||
// Returns a Button node whose text is exactly 4 digits (card last numbers)
|
||||
Node findCardButtonOnHomeScreen();
|
||||
|
||||
@ -73,6 +79,17 @@ public:
|
||||
// Finds a TextView with full card number (format "XXXX XXXX XXXX XXXX")
|
||||
QString findFullCardNumber();
|
||||
|
||||
bool isTransactionListScreen();
|
||||
|
||||
// Возвращает кнопки транзакций на экране "Операции" (до maxCount штук)
|
||||
QList<Node> findTransactionButtons(int maxCount);
|
||||
|
||||
// Парсит имя и сумму из текста кнопки: "NAME + 150 ₽ CATEGORY"
|
||||
TransactionInfo parseTransactionButtonText(const QString &text);
|
||||
|
||||
// Парсит детали транзакции: дату, имя отправителя, телефон
|
||||
TransactionInfo parseTransactionDetail();
|
||||
|
||||
void test();
|
||||
|
||||
private:
|
||||
|
||||
BIN
assets/ozon/card_banner.png
Normal file
|
After Width: | Height: | Size: 565 KiB |
746
assets/ozon/card_banner.xml
Normal file
@ -0,0 +1,746 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/action_bar_root"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/container"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/coordinator"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node NAF="true" index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/touch_outside" class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false"
|
||||
checked="false" clickable="true" enabled="true" focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint=""/>
|
||||
<node index="1" text="" resource-id="ru.ozon.fintech.finance:id/design_bottom_sheet"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,497][1080,2400]" drawing-order="2"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/base_bottom_sheet_top"
|
||||
class="android.widget.LinearLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="true"
|
||||
enabled="true" focusable="true" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,633][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/base_bottom_sheet_dragger"
|
||||
class="android.view.View" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,633][1080,691]" drawing-order="1" hint=""/>
|
||||
<node index="2" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/base_bottom_sheet_main_container"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,691][1080,2337]" drawing-order="3" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/cbottom_recycler"
|
||||
class="androidx.recyclerview.widget.RecyclerView"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false"
|
||||
checked="false" clickable="false" enabled="true" focusable="true"
|
||||
focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,691][1080,2337]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,691][1080,1251]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,691][1080,1251]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,691][1038,1251]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[42,691][1038,1251]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,691][1038,1251]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.ImageView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,691][1038,1251]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1251][1080,1476]" drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,1251][1080,1476]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0"
|
||||
text="Бесплатная карта для доступа к товарам за 1 ₽ на Ozon"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1293][1038,1455]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1476][1080,1644]" drawing-order="3" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,1476][1080,1644]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.LinearLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1497][1038,1623]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[42,1497][168,1623]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1497][168,1623]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.ImageView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1497][168,1623]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1509][1038,1611]" drawing-order="3"
|
||||
hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[210,1509][1038,1611]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.LinearLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1509][1038,1611]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1509][1038,1563]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1509][1038,1563]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0"
|
||||
text="Копите звёзды за покупки вне Ozon"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1509][1038,1563]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1563][1038,1611]"
|
||||
drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1563][1038,1611]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0"
|
||||
text="В любых магазинах и кафе"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1563][1038,1611]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="3" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1644][1080,1842]" drawing-order="4" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,1644][1080,1842]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.LinearLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1665][1038,1821]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[42,1665][168,1791]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1665][168,1791]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.ImageView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1665][168,1791]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1665][1038,1821]" drawing-order="3"
|
||||
hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[210,1665][1038,1821]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.LinearLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1665][1038,1821]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1665][1038,1773]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1665][1038,1773]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0"
|
||||
text="Списывайте звёзды и получайте товары за 1 ₽"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1665][1038,1773]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1773][1038,1821]"
|
||||
drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1773][1038,1821]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0"
|
||||
text="Выбирайте из любимых брендов на Ozon"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1773][1038,1821]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="4" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1842][1080,2010]" drawing-order="5" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,1842][1080,2010]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.LinearLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1863][1038,1989]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[42,1863][168,1989]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1863][168,1989]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.ImageView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1863][168,1989]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1875][1038,1977]" drawing-order="3"
|
||||
hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[210,1875][1038,1977]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.LinearLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1875][1038,1977]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1875][1038,1929]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1875][1038,1929]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0"
|
||||
text="Пользуйтесь картой бесплатно"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1875][1038,1929]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[210,1929][1038,1977]"
|
||||
drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1929][1038,1977]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0"
|
||||
text="Без комиссии за выпуск и обслуживание"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[210,1929][1038,1977]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="5" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,2010][1080,2241]" drawing-order="6" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,2010][1080,2241]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="true"
|
||||
enabled="true" focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,2052][1038,2199]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.ViewGroup"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[381,2099][699,2152]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text="Заказать карту"
|
||||
resource-id="ru.ozon.fintech.finance:id/mainViewId"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[381,2099][699,2152]"
|
||||
drawing-order="2" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[381,2099][699,2152]"
|
||||
drawing-order="4" hint=""/>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[381,2149][699,2152]"
|
||||
drawing-order="3" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="6" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,2241][1080,2337]" drawing-order="7" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,2241][1080,2337]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="Условия программы лояльности"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,2241][1038,2295]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>
|
||||
BIN
assets/ozon/google_play.png
Normal file
|
After Width: | Height: | Size: 2.0 MiB |
283
assets/ozon/google_play.xml
Normal file
@ -0,0 +1,283 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.FrameLayout" package="com.android.vending" content-desc="" checkable="false"
|
||||
checked="false" clickable="false" enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout"
|
||||
package="com.android.vending" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.FrameLayout" package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.view.ViewGroup" package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,136][1080,2337]" drawing-order="1" hint="">
|
||||
<node NAF="true" index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.view.View" package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="true" enabled="true" focusable="true"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,136][1080,2337]" drawing-order="1" hint=""/>
|
||||
<node index="1" text="" resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.FrameLayout" package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,1276][1080,2337]" drawing-order="2"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.LinearLayout" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1276][1080,2337]" drawing-order="1" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.ScrollView" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="true" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1276][1080,2169]" drawing-order="2" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.LinearLayout" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1276][1080,2169]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,1276][1080,2169]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.LinearLayout"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,1276][1080,2169]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[63,1276][1080,1418]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.LinearLayout"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[63,1318][1080,1397]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.ImageView"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[63,1326][126,1389]"
|
||||
drawing-order="1" hint=""/>
|
||||
<node index="1" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.ImageView"
|
||||
package="com.android.vending"
|
||||
content-desc="Google Play" checkable="false"
|
||||
checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[147,1326][352,1389]" drawing-order="2"
|
||||
hint=""/>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.FrameLayout"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[938,1276][1080,1418]" drawing-order="2"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.ImageView"
|
||||
package="com.android.vending"
|
||||
content-desc="Закрыть диалоговое окно с информацией об обновлении"
|
||||
checkable="false" checked="false" clickable="true"
|
||||
enabled="true" focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[938,1276][1080,1418]" drawing-order="1"
|
||||
hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.FrameLayout"
|
||||
package="com.android.vending"
|
||||
content-desc="Доступно обновление " checkable="false"
|
||||
checked="false" clickable="false" enabled="true"
|
||||
focusable="true" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[63,1418][1080,1570]" drawing-order="2" hint=""/>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.widget.LinearLayout"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[63,1570][1017,2169]"
|
||||
drawing-order="3" hint="">
|
||||
<node index="0"
|
||||
text="Чтобы использовать приложение, установите его последнюю версию. Во время скачивания обновлений вы по-прежнему сможете им пользоваться."
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.TextView"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[63,1570][1017,1784]" drawing-order="1"
|
||||
hint=""/>
|
||||
<node index="1" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.view.ViewGroup"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[63,1810][1017,1957]" drawing-order="2"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.ImageView"
|
||||
package="com.android.vending" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[63,1810][210,1957]"
|
||||
drawing-order="4" hint=""/>
|
||||
<node index="1" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.view.View"
|
||||
package="com.android.vending"
|
||||
content-desc="Приложение: Ozon Банк: выгодные покупки Рейтинг: 4,2. 13 МБ 3+ "
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[252,1810][1017,1917]"
|
||||
drawing-order="2" hint=""/>
|
||||
</node>
|
||||
<node index="2" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.FrameLayout"
|
||||
package="com.android.vending"
|
||||
content-desc="Что нового Последнее обновление: 15 февр. 2026 г. "
|
||||
checkable="false" checked="false" clickable="true"
|
||||
enabled="true" focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[63,1983][1017,2169]" drawing-order="3"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.ImageView"
|
||||
package="com.android.vending"
|
||||
content-desc="Развернуть" checkable="false"
|
||||
checked="false" clickable="true" enabled="true"
|
||||
focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[954,2045][1017,2108]" drawing-order="2"
|
||||
hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="2" text=""
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.LinearLayout" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,2169][1080,2337]" drawing-order="4" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.LinearLayout"
|
||||
package="com.android.vending" content-desc="" checkable="false"
|
||||
checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[63,2169][1017,2337]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="com.android.vending:id/button_group"
|
||||
class="android.view.ViewGroup" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[63,2190][1017,2316]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text="Подробнее"
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.Button" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false"
|
||||
clickable="true" enabled="true" focusable="true"
|
||||
focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[63,2201][524,2306]"
|
||||
drawing-order="1" hint=""/>
|
||||
<node index="1" text="Обновить"
|
||||
resource-id="com.android.vending:id/0_resource_name_obfuscated"
|
||||
class="android.widget.Button" package="com.android.vending"
|
||||
content-desc="" checkable="false" checked="false"
|
||||
clickable="true" enabled="true" focusable="true"
|
||||
focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[556,2201][1017,2306]" drawing-order="2" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>
|
||||
BIN
assets/ozon/pay/pay_to_new_number.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
1
assets/ozon/pay/pay_to_new_number.xml
Normal file
BIN
assets/ozon/pay/pay_to_old_number.png
Normal file
|
After Width: | Height: | Size: 169 KiB |
1
assets/ozon/pay/pay_to_old_number.xml
Normal file
BIN
assets/ozon/select_bank.png
Normal file
|
After Width: | Height: | Size: 132 KiB |
518
assets/ozon/select_bank.xml
Normal file
@ -0,0 +1,518 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/action_bar_root"
|
||||
class="android.widget.LinearLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/main_activity_top"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="1" text="" resource-id="ru.ozon.fintech.finance:id/over_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="4" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/over_fragment_top"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,0][1080,2400]" drawing-order="1"
|
||||
hint=""/>
|
||||
</node>
|
||||
<node index="3" text="" resource-id="" class="android.view.ViewGroup"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="3" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,0][1080,2400]" drawing-order="2"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/web_view_top"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/web_view_holder"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.webkit.WebView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]" drawing-order="0"
|
||||
hint="">
|
||||
<node index="0" text="⠀" resource-id=""
|
||||
class="android.webkit.WebView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="true" focused="true"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="svelte"
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="MFC2C@sbp"
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[0,0][1080,252]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="base-go-back-button"
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="true"
|
||||
enabled="true"
|
||||
focusable="true"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[44,149][133,236]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1"
|
||||
text="Кому перевести"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[372,165][708,223]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text=""
|
||||
resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="native-toolbar-close-button"
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="true"
|
||||
enabled="true"
|
||||
focusable="true"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[1021,191][1080,252]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[0,249][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[42,280][1039,430]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0"
|
||||
text="+7 (985) 553-28-96"
|
||||
resource-id="select-contact-input"
|
||||
class="android.widget.EditText"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="true"
|
||||
enabled="true"
|
||||
focusable="true"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[84,349][808,404]"
|
||||
drawing-order="0"
|
||||
hint="Владислав Николаевич К."/>
|
||||
<node index="1" text=""
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[42,280][1039,430]"
|
||||
drawing-order="0"
|
||||
hint=""/>
|
||||
<node index="2"
|
||||
text="Владислав Николаевич К."
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[84,307][488,351]"
|
||||
drawing-order="0"
|
||||
hint=""/>
|
||||
<node NAF="true" index="3"
|
||||
text="" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="true"
|
||||
enabled="true"
|
||||
focusable="true"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[931,322][997,388]"
|
||||
drawing-order="0"
|
||||
hint=""/>
|
||||
</node>
|
||||
<node index="1"
|
||||
text="В этом месяце можно перевести ещё 600 000 ₽ без комиссии"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[168,511][997,609]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text=""
|
||||
resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="internal-bank-item"
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="true"
|
||||
enabled="true"
|
||||
focusable="true"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[42,669][1039,829]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="3" text=""
|
||||
resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="100000000008"
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="true"
|
||||
enabled="true"
|
||||
focusable="true"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[42,826][1039,987]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="4" text=""
|
||||
resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="100000000004"
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="true"
|
||||
enabled="true"
|
||||
focusable="true"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[42,984][1039,1144]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="5" text=""
|
||||
resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="other-bank-item"
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="true"
|
||||
enabled="true"
|
||||
focusable="true"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[42,1141][1039,1302]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="modal-container"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,2336][1080,2336]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="⠀"
|
||||
resource-id="svelte-announcer"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][5,5]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/web_view_toolbar"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,147]" drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_bottom_navigation_holder"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,2201][1080,2400]" drawing-order="3"
|
||||
hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id="" class="android.view.View" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[818,0][1080,262]" drawing-order="2" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>
|
||||
BIN
assets/ozon/transaction_banner.png
Normal file
|
After Width: | Height: | Size: 167 KiB |
204
assets/ozon/transaction_banner.xml
Normal file
@ -0,0 +1,204 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.LinearLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/action_bar_root"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/container"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/coordinator"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node NAF="true" index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/touch_outside" class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false"
|
||||
checked="false" clickable="true" enabled="true" focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint=""/>
|
||||
<node index="1" text="" resource-id="ru.ozon.fintech.finance:id/design_bottom_sheet"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,1589][1080,2400]" drawing-order="2"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/base_bottom_sheet_top"
|
||||
class="android.widget.LinearLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="true"
|
||||
enabled="true" focusable="true" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1725][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/base_bottom_sheet_dragger"
|
||||
class="android.view.View" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1725][1080,1783]" drawing-order="1" hint=""/>
|
||||
<node index="2" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/base_bottom_sheet_main_container"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1783][1080,2337]" drawing-order="3" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/cbottom_recycler"
|
||||
class="androidx.recyclerview.widget.RecyclerView"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false"
|
||||
checked="false" clickable="false" enabled="true" focusable="true"
|
||||
focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,1783][1080,2337]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1783][1080,1966]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,1783][1080,1966]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="Можете указывать любое имя при оплате"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1783][1038,1945]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,1966][1080,2158]" drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,1966][1080,2158]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0"
|
||||
text="Например, OZON CLIENT или ваше имя и фамилию латиницей"
|
||||
resource-id="ru.ozon.fintech.finance:id/text_atom"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,1966][1038,2074]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,2158][1080,2337]" drawing-order="3" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,2158][1080,2337]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="true"
|
||||
enabled="true" focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[42,2158][1038,2274]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.ViewGroup"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[453,2189][626,2242]" drawing-order="1"
|
||||
hint="">
|
||||
<node index="0" text="Понятно"
|
||||
resource-id="ru.ozon.fintech.finance:id/mainViewId"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[453,2189][626,2242]"
|
||||
drawing-order="2" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[453,2189][626,2242]"
|
||||
drawing-order="4" hint=""/>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[453,2239][626,2242]"
|
||||
drawing-order="3" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>
|
||||
BIN
assets/ozon/transaction_document.png
Normal file
|
After Width: | Height: | Size: 283 KiB |
100
assets/ozon/transaction_document.xml
Normal file
@ -0,0 +1,100 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.view.ViewGroup" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/web_view_toolbar"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,136][1080,283]" drawing-order="2" hint="">
|
||||
<node index="0" text="Терехина_Г_Г_о_проведении_операции_ozonbank_document_24959706.pdf"
|
||||
resource-id="ru.ozon.fintech.finance:id/root_title" class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[42,182][932,236]"
|
||||
drawing-order="1" hint=""/>
|
||||
<node index="1" text="" resource-id="" class="androidx.appcompat.widget.LinearLayoutCompat"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[932,136][1080,283]"
|
||||
drawing-order="4" hint="">
|
||||
<node NAF="true" index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/fintech_menu_close"
|
||||
class="android.widget.Button" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="true" enabled="true" focusable="true"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[932,146][1080,272]" drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id="" class="androidx.viewpager.widget.ViewPager"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="true"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,283][1080,2337]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="androidx.recyclerview.widget.RecyclerView"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="true" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,283][1080,2337]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,283][1080,2337]"
|
||||
drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false"
|
||||
checked="false" clickable="false" enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,283][1080,2337]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false"
|
||||
checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,283][1080,2337]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/pv_pdf"
|
||||
class="android.widget.ImageView" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,283][1080,2337]" drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="2" text="" resource-id="ru.ozon.fintech.finance:id/share_btn"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="true" enabled="true" focusable="true"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[42,2148][1038,2295]" drawing-order="7" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance" content-desc="Поделиться" checkable="false"
|
||||
checked="false" clickable="true" enabled="true" focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[42,2148][1038,2295]" drawing-order="1" hint="">
|
||||
<node index="0" text="Поделиться" resource-id="" class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[418,2190][663,2253]"
|
||||
drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>
|
||||
BIN
assets/ozon/transaction_info.png
Normal file
|
After Width: | Height: | Size: 83 KiB |
502
assets/ozon/transaction_info.xml
Normal file
@ -0,0 +1,502 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/action_bar_root"
|
||||
class="android.widget.LinearLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/main_activity_top"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="1" text="" resource-id="ru.ozon.fintech.finance:id/over_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="4" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/over_fragment_top"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,0][1080,2400]" drawing-order="1"
|
||||
hint=""/>
|
||||
</node>
|
||||
<node index="3" text="" resource-id="" class="android.view.ViewGroup"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="3" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,0][1080,2400]" drawing-order="2"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/web_view_top"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/web_view_holder"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.webkit.WebView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]" drawing-order="0"
|
||||
hint="">
|
||||
<node index="0" text="15.01.2026, 15:55" resource-id=""
|
||||
class="android.webkit.WebView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="true" focused="true"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="svelte"
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,241]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="base-go-back-button"
|
||||
checkable="false" checked="false"
|
||||
clickable="true" enabled="true"
|
||||
focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[28,144][118,231]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="15.01.2026, 15:55"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[367,160][711,215]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="native-toolbar-close-button"
|
||||
checkable="false" checked="false"
|
||||
clickable="true" enabled="true"
|
||||
focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[1036,186][1080,241]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,238][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[42,301][1039,771]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[42,301][1039,771]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0"
|
||||
text="Успешное пополнение"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,364][619,425]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="+ 150 ₽"
|
||||
resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,441][333,530]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[847,364][976,493]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[847,364][976,493]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text=""
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[926,443][992,509]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="3" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,556][976,609]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0"
|
||||
text="CARD2CARD AMOBILE"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[105,553][556,611]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text=""
|
||||
resource-id=""
|
||||
class="android.widget.Image"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[564,574][632,598]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="Ozon Карта"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[640,553][876,611]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="4" text="Финансовые услуги"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,627][509,685]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="5" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[522,635][567,679]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="Документы" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="true"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[42,787][530,1008]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="Оспорить" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="true"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[548,787][1039,1008]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="3" text=""
|
||||
resource-id="MFInlineVoc@self-service"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[42,1005][1039,1018]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="2" text=""
|
||||
resource-id="modal-container"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,2336][1080,2336]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="3" text="⠀"
|
||||
resource-id="svelte-announcer"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][5,5]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/web_view_toolbar"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,147]" drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_bottom_navigation_holder"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,2201][1080,2400]" drawing-order="3"
|
||||
hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id="" class="android.view.View" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[818,0][1080,262]" drawing-order="2" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>
|
||||
BIN
assets/ozon/transaction_info_spb.png
Normal file
|
After Width: | Height: | Size: 100 KiB |
509
assets/ozon/transaction_info_spb.xml
Normal file
@ -0,0 +1,509 @@
|
||||
<?xml version='1.0' encoding='UTF-8' standalone='yes' ?>
|
||||
<hierarchy rotation="0">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="" class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/action_bar_root"
|
||||
class="android.widget.LinearLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="android:id/content" class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/main_activity_top"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="1" text="" resource-id="ru.ozon.fintech.finance:id/over_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2400]" drawing-order="4" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/over_fragment_top"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,0][1080,2400]" drawing-order="1"
|
||||
hint=""/>
|
||||
</node>
|
||||
<node index="3" text="" resource-id="" class="android.view.ViewGroup"
|
||||
package="ru.ozon.fintech.finance" content-desc="" checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false" bounds="[0,0][1080,2400]"
|
||||
drawing-order="3" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,0][1080,2400]" drawing-order="2"
|
||||
hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_host_fragment"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id="ru.ozon.fintech.finance:id/web_view_top"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2400]" drawing-order="1" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/web_view_holder"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="2" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.FrameLayout"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="1" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.webkit.WebView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]" drawing-order="0"
|
||||
hint="">
|
||||
<node index="0" text="13.01.2026, 12:42" resource-id=""
|
||||
class="android.webkit.WebView"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="true" focused="true"
|
||||
scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[0,0][1080,2337]" drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id="svelte"
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,241]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="base-go-back-button"
|
||||
checkable="false" checked="false"
|
||||
clickable="true" enabled="true"
|
||||
focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[28,144][118,231]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="13.01.2026, 12:42"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[370,160][708,215]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="native-toolbar-close-button"
|
||||
checkable="false" checked="false"
|
||||
clickable="true" enabled="true"
|
||||
focusable="true" focused="false"
|
||||
scrollable="false" long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[1036,186][1080,241]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,238][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[42,301][1039,916]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[42,301][1039,916]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0"
|
||||
text="Успешное пополнение"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,364][619,425]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="+ 100 ₽"
|
||||
resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,441][336,530]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[847,364][976,493]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[847,364][976,493]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text=""
|
||||
resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[926,443][992,509]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text=""
|
||||
resource-id=""
|
||||
class="android.widget.Image"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc=""
|
||||
checkable="false"
|
||||
checked="false"
|
||||
clickable="false"
|
||||
enabled="true"
|
||||
focusable="false"
|
||||
focused="false"
|
||||
scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false"
|
||||
selected="false"
|
||||
bounds="[942,443][976,509]"
|
||||
drawing-order="0"
|
||||
hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="3" text="Альфа-Банк"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,532][349,590]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="4" text="" resource-id=""
|
||||
class="android.widget.Image"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[367,548][435,574]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="5" text="Основной счёт"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[454,535][758,588]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="6"
|
||||
text="Владислав Николаевич К."
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,603][627,661]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="7"
|
||||
text="+7 (985) 553-28-96"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[105,664][976,716]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="8"
|
||||
text="Перевод денежных средств"
|
||||
resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false"
|
||||
password="false" selected="false"
|
||||
bounds="[341,756][976,853]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="Вернуть деньги"
|
||||
resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="true"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[42,931][530,1152]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="2" text="Документы" resource-id=""
|
||||
class="android.widget.Button"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="true"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[548,931][1039,1152]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="3" text=""
|
||||
resource-id="MFInlineVoc@self-service"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false"
|
||||
bounds="[42,1147][1039,1160]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="2" text=""
|
||||
resource-id="modal-container"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,2336][1080,2336]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="3" text="⠀"
|
||||
resource-id="svelte-announcer"
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][5,5]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
<node index="2" text="" resource-id=""
|
||||
class="android.view.View"
|
||||
package="ru.ozon.fintech.finance" content-desc=""
|
||||
checkable="false" checked="false"
|
||||
clickable="false" enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint="">
|
||||
<node index="0" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
<node index="1" text="" resource-id=""
|
||||
class="android.widget.TextView"
|
||||
package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false"
|
||||
checked="false" clickable="false"
|
||||
enabled="true" focusable="false"
|
||||
focused="false" scrollable="false"
|
||||
long-clickable="false" password="false"
|
||||
selected="false" bounds="[0,0][1080,2337]"
|
||||
drawing-order="0" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/web_view_toolbar"
|
||||
class="android.view.ViewGroup" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false"
|
||||
enabled="true" focusable="false" focused="false" scrollable="false"
|
||||
long-clickable="false" password="false" selected="false"
|
||||
bounds="[0,0][1080,147]" drawing-order="1" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text=""
|
||||
resource-id="ru.ozon.fintech.finance:id/main_activity_bottom_navigation_holder"
|
||||
class="android.widget.FrameLayout" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false"
|
||||
password="false" selected="false" bounds="[0,2201][1080,2400]" drawing-order="3"
|
||||
hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
<node index="1" text="" resource-id="" class="android.view.View" package="ru.ozon.fintech.finance"
|
||||
content-desc="" checkable="false" checked="false" clickable="false" enabled="true"
|
||||
focusable="false" focused="false" scrollable="false" long-clickable="false" password="false"
|
||||
selected="false" bounds="[818,0][1080,262]" drawing-order="2" hint=""/>
|
||||
</node>
|
||||
</node>
|
||||
</node>
|
||||
</hierarchy>
|
||||
@ -2,13 +2,13 @@
|
||||
list=ozon
|
||||
|
||||
[common]
|
||||
access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNDU1MjE1Ni1kMTc5LTRhNzktOWU5MS01ODcwMzhjOTVjZjAiLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcyMjQ4MzM1fQ.gDA57WCWCCwdrxa84McLJLcIyV4RYLRQ7s9YT3AcFd4
|
||||
access_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjNDU1MjE1Ni1kMTc5LTRhNzktOWU5MS01ODcwMzhjOTVjZjAiLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzcyNDE4MDUwfQ.26k6Z22gMOzNFuS17Mx0d4dt8pnJQh9TIv0rrAk2Xro
|
||||
api_key=0eee77b62013253e02e1a69bc6a505bb37757fb7
|
||||
currencies=USD, KRW, AZN, RUB
|
||||
desktop_id=b86eac8e-dc89-4a9e-a5e8-7f85db011391
|
||||
profile_id=c4552156-d179-4a79-9e91-587038c95cf0
|
||||
refresh_token=2cdf2bd7-9d5f-463f-98f4-fce79a9ee9a4
|
||||
token_expires_at=1772248335
|
||||
refresh_token=ff49cde7-4f3a-4efa-ad46-84514fc03bf6
|
||||
token_expires_at=1772418050
|
||||
version=0.0.1
|
||||
|
||||
[db]
|
||||
|
||||
@ -271,6 +271,35 @@ void DatabaseManager::initSchema() {
|
||||
q.exec("PRAGMA user_version = 7");
|
||||
qDebug() << "[DB] Migration 7 applied";
|
||||
}
|
||||
|
||||
// ── Версия 8: receipt_image в transactions ──────────────────────────────────
|
||||
if (version < 8) {
|
||||
db.transaction();
|
||||
|
||||
if (!columnExists(q, "transactions", "receipt_image")) {
|
||||
q.exec("ALTER TABLE transactions ADD COLUMN receipt_image BLOB");
|
||||
}
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 8");
|
||||
qDebug() << "[DB] Migration 8 applied";
|
||||
}
|
||||
|
||||
// ── Версия 9: таблица banks ───────────────────────────────────────────────
|
||||
if (version < 9) {
|
||||
db.transaction();
|
||||
|
||||
q.exec(R"(CREATE TABLE IF NOT EXISTS banks (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
alias TEXT UNIQUE,
|
||||
name TEXT,
|
||||
nspk_name TEXT
|
||||
))");
|
||||
|
||||
db.commit();
|
||||
q.exec("PRAGMA user_version = 9");
|
||||
qDebug() << "[DB] Migration 9 applied";
|
||||
}
|
||||
}
|
||||
|
||||
void DatabaseManager::closeConnection() {
|
||||
|
||||
@ -167,6 +167,30 @@ AccountInfo AccountDAO::getAccountById(const int accountId) {
|
||||
return {};
|
||||
}
|
||||
|
||||
QList<AccountInfo> AccountDAO::getAccountsByAppCode(const QString &appCode) {
|
||||
QList<AccountInfo> accounts;
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
FROM accounts
|
||||
WHERE app_code = :app_code
|
||||
)");
|
||||
|
||||
query.bindValue(":app_code", appCode);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении accounts по app_code:" << query.lastError().text();
|
||||
return accounts;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
accounts.append(parseAccount(query));
|
||||
}
|
||||
|
||||
return accounts;
|
||||
}
|
||||
|
||||
AccountInfo AccountDAO::findAppAccount(const QString &deviceId, const QString &appName, const QString &lastNumber) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
@ -192,3 +216,27 @@ AccountInfo AccountDAO::findAppAccount(const QString &deviceId, const QString &a
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
AccountInfo AccountDAO::findByMaterialId(const QString &materialId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_code, last_numbers, card_number, material_id, amount, update_time, status, description, currency
|
||||
FROM accounts
|
||||
WHERE material_id = :material_id
|
||||
LIMIT 1
|
||||
)");
|
||||
|
||||
query.bindValue(":material_id", materialId);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при поиске account по material_id:" << query.lastError().text();
|
||||
return {};
|
||||
}
|
||||
|
||||
if (query.next()) {
|
||||
return parseAccount(query);
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
@ -22,9 +22,13 @@ public:
|
||||
|
||||
[[nodiscard]] static AccountInfo getAccountById(int accountId);
|
||||
|
||||
[[nodiscard]] static QList<AccountInfo> getAccountsByAppCode(const QString &appCode);
|
||||
|
||||
[[nodiscard]] static AccountInfo findAppAccount(
|
||||
const QString &deviceId,
|
||||
const QString &appName,
|
||||
const QString &lastNumber
|
||||
);
|
||||
|
||||
[[nodiscard]] static AccountInfo findByMaterialId(const QString &materialId);
|
||||
};
|
||||
|
||||
55
database/dao/BankDAO.cpp
Normal file
@ -0,0 +1,55 @@
|
||||
#include "BankDAO.h"
|
||||
|
||||
#include <QSqlQuery>
|
||||
#include <QSqlError>
|
||||
#include <QVariant>
|
||||
|
||||
#include "DatabaseManager.h"
|
||||
|
||||
QList<BankInfo> BankDAO::getAll() {
|
||||
QList<BankInfo> list;
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare("SELECT * FROM banks ORDER BY alias");
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при получении banks:" << query.lastError().text();
|
||||
return list;
|
||||
}
|
||||
|
||||
while (query.next()) {
|
||||
BankInfo bank;
|
||||
bank.id = query.value("id").toInt();
|
||||
bank.alias = query.value("alias").toString();
|
||||
bank.name = query.value("name").toString();
|
||||
bank.nspkName = query.value("nspk_name").toString();
|
||||
list.append(bank);
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
void BankDAO::replaceAll(const QList<BankInfo> &banks) {
|
||||
QSqlDatabase db = DatabaseManager::instance().database();
|
||||
QSqlQuery query(db);
|
||||
|
||||
db.transaction();
|
||||
|
||||
query.exec("DELETE FROM banks");
|
||||
|
||||
query.prepare(R"(
|
||||
INSERT INTO banks (alias, name, nspk_name)
|
||||
VALUES (:alias, :name, :nspk_name)
|
||||
)");
|
||||
|
||||
for (const BankInfo &bank : banks) {
|
||||
query.bindValue(":alias", bank.alias);
|
||||
query.bindValue(":name", bank.name);
|
||||
query.bindValue(":nspk_name", bank.nspkName);
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при вставке bank:" << query.lastError().text();
|
||||
}
|
||||
}
|
||||
|
||||
db.commit();
|
||||
}
|
||||
11
database/dao/BankDAO.h
Normal file
@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
|
||||
#include "db/BankInfo.h"
|
||||
#include <QList>
|
||||
|
||||
class BankDAO {
|
||||
public:
|
||||
[[nodiscard]] static QList<BankInfo> getAll();
|
||||
|
||||
static void replaceAll(const QList<BankInfo> &banks);
|
||||
};
|
||||
@ -11,7 +11,7 @@ EventInfo parseEvent(const QSqlQuery &query) {
|
||||
event.id = query.value("id").toInt();
|
||||
event.status = eventStatusFromString(query.value("status").toString());
|
||||
event.type = eventTypeFromString(query.value("type").toString());
|
||||
event.externalId = query.value("external_id").toInt();
|
||||
event.externalId = query.value("external_id").toString();
|
||||
event.accountId = query.value("account_id").toInt();
|
||||
event.deviceId = query.value("device_id").toString();
|
||||
event.amount = query.value("amount").toDouble();
|
||||
@ -146,3 +146,20 @@ EventInfo EventDAO::getFirstWaitEventByDeviceId(const QString &key) {
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool EventDAO::existsActiveByExternalId(const QString &externalId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
SELECT 1 FROM events
|
||||
WHERE external_id = :external_id
|
||||
AND status IN ('wait', 'iPprogress')
|
||||
LIMIT 1
|
||||
)");
|
||||
query.bindValue(":external_id", externalId);
|
||||
|
||||
if (!query.exec()) {
|
||||
qCritical() << "Ошибка при existsActiveByExternalId:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return query.next();
|
||||
}
|
||||
|
||||
@ -15,4 +15,6 @@ public:
|
||||
[[nodiscard]] static QList<EventInfo> getAllEvents(const EventStatus &status);
|
||||
|
||||
[[nodiscard]] static EventInfo getFirstWaitEventByDeviceId(const QString &key);
|
||||
|
||||
[[nodiscard]] static bool existsActiveByExternalId(const QString &externalId);
|
||||
};
|
||||
|
||||
@ -16,17 +16,19 @@ bool TransactionDAO::insertTransactionIfNotExists(const TransactionInfo &info) {
|
||||
SELECT COUNT(*) FROM transactions
|
||||
WHERE account_id = :account_id AND amount = :amount AND timestamp = :timestamp AND type = :type
|
||||
)");
|
||||
query.bindValue(":timestamp", DateUtils::toUtcString(info.timestamp));
|
||||
} else {
|
||||
query.prepare(R"(
|
||||
SELECT COUNT(*) FROM transactions
|
||||
WHERE account_id = :account_id AND amount = :amount AND bankTime = :bankTime AND type = :type
|
||||
WHERE account_id = :account_id AND amount = :amount AND bank_time = :bank_time AND bank_name = :bank_name
|
||||
)");
|
||||
query.bindValue(":bank_time", info.bankTime);
|
||||
query.bindValue(":bank_name", info.bankName);
|
||||
}
|
||||
|
||||
query.bindValue(":account_id", info.accountId);
|
||||
query.bindValue(":amount", info.amount);
|
||||
query.bindValue(":type", transactionTypeToString(info.type));
|
||||
query.bindValue(":timestamp", DateUtils::toUtcString(info.timestamp));
|
||||
|
||||
if (!query.exec() || !query.next()) {
|
||||
qWarning() << "Ошибка при проверке существования transaction:" << query.lastError().text();
|
||||
@ -140,6 +142,7 @@ TransactionInfo parseTransaction(const QSqlQuery &query) {
|
||||
tx.updateTime = DateUtils::fromUtcString(query.value("update_time").toString());
|
||||
tx.completeTime = DateUtils::fromUtcString(query.value("complete_time").toString());
|
||||
tx.timestamp = DateUtils::fromUtcString(query.value("timestamp").toString());
|
||||
tx.receiptImage = query.value("receipt_image").toByteArray();
|
||||
return tx;
|
||||
}
|
||||
|
||||
@ -344,3 +347,37 @@ bool TransactionDAO::updateExternalTransactionId(const int id, const int externa
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TransactionDAO::updateReceiptImage(const int id, const QByteArray &image) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare("UPDATE transactions SET receipt_image = :image WHERE id = :id");
|
||||
query.bindValue(":id", id);
|
||||
query.bindValue(":image", image);
|
||||
|
||||
if (!query.exec()) {
|
||||
qWarning() << "Ошибка при сохранении receipt_image:" << query.lastError().text();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
TransactionInfo TransactionDAO::findByBankTimeAndAmount(
|
||||
const int accountId, const QString &bankName, const QString &bankTime, const double amount
|
||||
) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
query.prepare(R"(
|
||||
SELECT * FROM transactions
|
||||
WHERE account_id = :account_id AND bank_name = :bank_name
|
||||
AND bank_time = :bank_time AND amount = :amount
|
||||
LIMIT 1
|
||||
)");
|
||||
query.bindValue(":account_id", accountId);
|
||||
query.bindValue(":bank_name", bankName);
|
||||
query.bindValue(":bank_time", bankTime);
|
||||
query.bindValue(":amount", amount);
|
||||
|
||||
if (!query.exec() || !query.next()) {
|
||||
return {};
|
||||
}
|
||||
return parseTransaction(query);
|
||||
}
|
||||
|
||||
@ -26,4 +26,10 @@ public:
|
||||
);
|
||||
|
||||
[[nodiscard]] static bool updateExternalTransactionId(int id, int externalId);
|
||||
|
||||
[[nodiscard]] static bool updateReceiptImage(int id, const QByteArray &image);
|
||||
|
||||
// Найти транзакцию по bank_name + bank_time + amount
|
||||
[[nodiscard]] static TransactionInfo findByBankTimeAndAmount(
|
||||
int accountId, const QString &bankName, const QString &bankTime, double amount);
|
||||
};
|
||||
|
||||
65
docs/api/auth.md
Normal file
@ -0,0 +1,65 @@
|
||||
# Auth API
|
||||
|
||||
[← Главная](../index.md)
|
||||
|
||||
Авторизация через API-ключ и управление токенами.
|
||||
|
||||
Все защищённые эндпоинты требуют заголовок:
|
||||
```
|
||||
Authorization: Bearer <access_token>
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/auth/login`
|
||||
|
||||
Получение токена по API-ключу.
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"api_key": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"access_token": "string",
|
||||
"refresh_token": "string",
|
||||
"expires_in": 3600.0
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/auth/refresh_token`
|
||||
|
||||
Обновление access_token по refresh_token.
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"refresh_token": "string"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ:** аналогичен `/login`.
|
||||
|
||||
---
|
||||
|
||||
## Хранение токенов в проекте
|
||||
|
||||
Токены сохраняются в `config.ini`:
|
||||
|
||||
| Ключ | Значение |
|
||||
|------|----------|
|
||||
| `common/access_token` | Bearer-токен для запросов |
|
||||
| `common/refresh_token` | Для обновления |
|
||||
| `common/token_expires_at` | Unix timestamp истечения |
|
||||
|
||||
Рефреш токена происходит автоматически за 30 минут до истечения — см. `NetworkService::scheduleNextRefresh()`.
|
||||
111
docs/api/device.md
Normal file
@ -0,0 +1,111 @@
|
||||
# Device & Desktop API
|
||||
|
||||
[← Главная](../index.md)
|
||||
|
||||
Управление десктопами (установками приложения) и устройствами (подключёнными Android телефонами).
|
||||
|
||||
---
|
||||
|
||||
## Desktop
|
||||
|
||||
Desktop — это одна установка ARCDeskProject. Каждый desktop содержит список устройств.
|
||||
|
||||
### GET `/api/v1/desktop/` 🔒
|
||||
|
||||
Получить все десктопы профиля.
|
||||
|
||||
### POST `/api/v1/desktop/` 🔒
|
||||
|
||||
Зарегистрировать новый десктоп.
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"blue_token": "string",
|
||||
"name": "Мой компьютер",
|
||||
"contact": "string",
|
||||
"app_version": "1.0.0"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ** содержит `id` (UUID) и `unique_name`.
|
||||
|
||||
### PATCH `/api/v1/desktop/{desktop_id}` 🔒
|
||||
|
||||
Обновить данные десктопа (версию, session_time и т.д.).
|
||||
|
||||
---
|
||||
|
||||
## Device
|
||||
|
||||
Device — физическое Android-устройство, подключённое к десктопу через ADB.
|
||||
|
||||
### GET `/api/v1/device/{desktop_id}` 🔒
|
||||
|
||||
Получить все устройства десктопа.
|
||||
|
||||
### POST `/api/v1/device/` 🔒
|
||||
|
||||
Зарегистрировать новое устройство.
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"desktop_id": "uuid",
|
||||
"status": "ONLINE",
|
||||
"name": "Pixel 6",
|
||||
"screen_width": 1080,
|
||||
"screen_height": 2400,
|
||||
"battery": 85
|
||||
}
|
||||
```
|
||||
|
||||
**`DeviceStatus`:** `"ONLINE"` | `"OFFLINE"`
|
||||
|
||||
**Ответ** содержит `id` (UUID) — сохраняется как `api_id` в таблице `devices`.
|
||||
|
||||
### PATCH `/api/v1/device/{device_id}` 🔒
|
||||
|
||||
Обновить статус, battery, update_time устройства.
|
||||
|
||||
```json
|
||||
{
|
||||
"status": "ONLINE",
|
||||
"battery": 72,
|
||||
"update_time": 1700000000
|
||||
}
|
||||
```
|
||||
|
||||
### POST `/api/v1/device/add_screenshot/{device_id}` 🔒
|
||||
|
||||
Загрузить скриншот экрана устройства.
|
||||
|
||||
**Тип запроса:** `multipart/form-data`
|
||||
|
||||
| Поле | Тип | Описание |
|
||||
|------|-----|----------|
|
||||
| `file` | binary | JPEG-скриншот |
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"device_id": "uuid",
|
||||
"screenshot_url": "https://...",
|
||||
"taken_at": "2024-01-01T12:00:00"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Связь с кодом проекта
|
||||
|
||||
| API | Метод в коде |
|
||||
|-----|-------------|
|
||||
| `POST /api/v1/desktop/` | `NetworkService::loginAndSetup()` |
|
||||
| `POST /api/v1/device/` | `NetworkService::createDevice()` |
|
||||
| `PATCH /api/v1/device/{id}` | `NetworkService::updateDevice()` |
|
||||
| `POST /api/v1/device/add_screenshot/{id}` | `NetworkService::postDeviceScreenshot()` |
|
||||
127
docs/api/profile.md
Normal file
@ -0,0 +1,127 @@
|
||||
# Profile API
|
||||
|
||||
[← Главная](../index.md)
|
||||
|
||||
Управление профилем, банковскими профилями и материалами (картами).
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/profile/current_profile` 🔒
|
||||
|
||||
Получить текущий профиль со всеми банковскими профилями.
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": {
|
||||
"id": "uuid",
|
||||
"api_key": "string",
|
||||
"bank_profiles": [
|
||||
{
|
||||
"id": "uuid",
|
||||
"bank_profile_external_id": 1,
|
||||
"first_name": "string",
|
||||
"last_name": "string",
|
||||
"middle_name": "string",
|
||||
"bank_name": "ozon",
|
||||
"phone_number": "+7...",
|
||||
"balance": "15000",
|
||||
"state": "active",
|
||||
"currency": 643,
|
||||
"profile_id": "uuid",
|
||||
"materials": [ ... ],
|
||||
"created_at": "2024-01-01T00:00:00",
|
||||
"updated_at": "2024-01-01T00:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/profile/fetch_bank_profiles` 🔒
|
||||
|
||||
Получить список всех банковских профилей.
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/profile/fetch_materials` 🔒
|
||||
|
||||
Получить список всех материалов (карт).
|
||||
|
||||
---
|
||||
|
||||
## Bank Profile
|
||||
|
||||
### POST `/api/v1/profile/bank_profile` 🔒
|
||||
|
||||
Создать банковский профиль.
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"first_name": "Иван",
|
||||
"last_name": "Иванов",
|
||||
"middle_name": "Иванович",
|
||||
"bank_name": "ozon",
|
||||
"phone_number": "+79001234567",
|
||||
"balance": "15000",
|
||||
"state": "active",
|
||||
"currency": 643
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ** содержит `id` (UUID) — сохраняется как `bank_profile_id` в таблице `applications`.
|
||||
|
||||
### PATCH `/api/v1/profile/bank_profile/{bank_profile_id}` 🔒
|
||||
|
||||
Обновить данные банковского профиля (любые поля опциональны).
|
||||
|
||||
### PATCH `/api/v1/profile/bank_profile/{bank_profile_id}/{is_enable}` 🔒
|
||||
|
||||
Включить (`true`) / отключить (`false`) банковский профиль.
|
||||
|
||||
---
|
||||
|
||||
## Material (карта)
|
||||
|
||||
Material — это карта/счёт внутри банковского профиля.
|
||||
|
||||
### POST `/api/v1/profile/material` 🔒
|
||||
|
||||
Создать материал.
|
||||
|
||||
**Тело запроса:**
|
||||
```json
|
||||
{
|
||||
"bank_profile_id": "uuid",
|
||||
"card_number": "1234567890123456",
|
||||
"name": "Основная карта"
|
||||
}
|
||||
```
|
||||
|
||||
**Ответ** содержит `id` (UUID) и `external_material_id` (int) — сохраняется как `material_id` в таблице `accounts`.
|
||||
|
||||
### PATCH `/api/v1/profile/material/{material_id}` 🔒
|
||||
|
||||
Обновить номер карты материала.
|
||||
|
||||
```json
|
||||
{ "card_number": "1234567890123456" }
|
||||
```
|
||||
|
||||
### PATCH `/api/v1/profile/material/{material_id}/{is_enable}` 🔒
|
||||
|
||||
Включить / отключить материал.
|
||||
|
||||
---
|
||||
|
||||
## Связь с кодом проекта
|
||||
|
||||
| API | Метод в коде |
|
||||
|-----|-------------|
|
||||
| `POST /api/v1/profile/bank_profile` | `NetworkService::postBankProfile()` |
|
||||
| `PATCH /api/v1/profile/bank_profile/{id}` | `NetworkService::patchBankProfile()` |
|
||||
| `POST /api/v1/profile/material` | `NetworkService::postMaterial()` |
|
||||
181
docs/api/task.md
Normal file
@ -0,0 +1,181 @@
|
||||
# Task API
|
||||
|
||||
[← Главная](../index.md)
|
||||
|
||||
Управление задачами: получение очереди, добавление задач, отправка результатов.
|
||||
|
||||
---
|
||||
|
||||
## GET `/api/v1/task/get_tasks` 🔒
|
||||
|
||||
Получить список задач в очереди для текущего профиля.
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{
|
||||
"bank_name": "ozon",
|
||||
"material_id": 42,
|
||||
"type": "FETCH_PROFILE",
|
||||
"body": {}
|
||||
},
|
||||
{
|
||||
"bank_name": "birbank",
|
||||
"material_id": 7,
|
||||
"type": "FETCH_TRANSACTIONS",
|
||||
"body": {}
|
||||
},
|
||||
{
|
||||
"bank_name": "ozon",
|
||||
"material_id": 5,
|
||||
"type": "CREATE_TRANSACTION",
|
||||
"body": {
|
||||
"amount": 1000,
|
||||
"currency": 643,
|
||||
"type": "bank",
|
||||
"bank_participator_id": "string",
|
||||
"extra": {}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
**Типы задач:**
|
||||
|
||||
| `type` | Описание | `body` |
|
||||
|--------|----------|--------|
|
||||
| `FETCH_PROFILE` | Получить баланс и данные аккаунта | пустой `{}` |
|
||||
| `FETCH_TRANSACTIONS` | Получить историю транзакций | пустой `{}` |
|
||||
| `CREATE_TRANSACTION` | Выполнить перевод | `CreateTransactionBody` |
|
||||
|
||||
**`CreateTransactionBody`:**
|
||||
```json
|
||||
{
|
||||
"amount": 1000,
|
||||
"currency": 643,
|
||||
"type": "bank",
|
||||
"bank_participator_id": "string",
|
||||
"extra": {}
|
||||
}
|
||||
```
|
||||
|
||||
- `currency` — ISO 4217 numeric код валюты (643 = RUB, 840 = USD, 944 = AZN, 410 = KRW)
|
||||
- `type` — `"bank"` или `"top-up"`
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/task/task_result` 🔒
|
||||
|
||||
Отправить результат выполненной задачи.
|
||||
|
||||
Тело запроса зависит от типа задачи (discriminator — поле `type`).
|
||||
|
||||
### FETCH_PROFILE
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_PROFILE",
|
||||
"material_id": 42,
|
||||
"bank_name": "ozon",
|
||||
"data": {
|
||||
"bank_account_id": "string",
|
||||
"currency": 643,
|
||||
"amount": 15000,
|
||||
"extra": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### FETCH_TRANSACTIONS
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_TRANSACTIONS",
|
||||
"material_id": 7,
|
||||
"bank_name": "birbank",
|
||||
"data": [
|
||||
{
|
||||
"bank_transaction_id": "string",
|
||||
"amount": 500,
|
||||
"currency": 944,
|
||||
"transaction_type": "bank",
|
||||
"bank_participator_id": "string",
|
||||
"status": "completed",
|
||||
"extra": {},
|
||||
"created_at": "2024-01-01T12:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### CREATE_TRANSACTION
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "CREATE_TRANSACTION",
|
||||
"material_id": 5,
|
||||
"bank_name": "ozon",
|
||||
"data": {
|
||||
"bank_transaction_id": "string",
|
||||
"amount": 1000,
|
||||
"currency": 643,
|
||||
"transaction_type": "bank",
|
||||
"bank_participator_id": "string",
|
||||
"status": "completed",
|
||||
"extra": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**`TransactionStatusEnum`:** `"progress"` | `"completed"` | `"declined"`
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/task/add_task`
|
||||
|
||||
Добавить задачу в очередь (вызывается внешней системой, не клиентом).
|
||||
|
||||
---
|
||||
|
||||
## POST `/api/v1/task/add_bank`
|
||||
|
||||
Зарегистрировать банк с webhook-URL.
|
||||
|
||||
**Query параметры:**
|
||||
|
||||
| Параметр | Тип | Описание |
|
||||
|----------|-----|----------|
|
||||
| `bank_name` | string | Название банка |
|
||||
| `webhook_url` | uri | URL для уведомлений |
|
||||
|
||||
---
|
||||
|
||||
## Ошибка выполнения задачи
|
||||
|
||||
Если задача не может быть выполнена (аккаунт не найден, скрипт упал), в поле `data` передаётся:
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_PROFILE",
|
||||
"material_id": 42,
|
||||
"bank_name": "ozon",
|
||||
"data": {
|
||||
"status": "error",
|
||||
"description": "Account not found for material_id: 42"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Связь с кодом проекта
|
||||
|
||||
| API | Метод в коде |
|
||||
|-----|-------------|
|
||||
| `GET /api/v1/task/get_tasks` | `NetworkService::getTasks()` |
|
||||
| `POST /api/v1/task/task_result` | `NetworkService::postTaskResult()` |
|
||||
|
||||
Хелпер в `EventHandler`: `sendTaskResult(type, materialId, bankName, data)` — создаёт `NetworkService` в отдельном потоке и вызывает `postTaskResult()`.
|
||||
237
docs/events.md
Normal file
@ -0,0 +1,237 @@
|
||||
# События и задачи
|
||||
|
||||
[← Главная](index.md)
|
||||
|
||||
Описание полного цикла задачи: от получения из API до завершения и отчёта.
|
||||
|
||||
---
|
||||
|
||||
## Ключевые файлы
|
||||
|
||||
| Файл | Роль |
|
||||
|------|------|
|
||||
| `services/EventHandler.h/cpp` | Оркестратор: поллинг, сохранение в БД, запуск потоков |
|
||||
| `services/net/NetworkService.h/cpp` | HTTP-клиент: `getTasks()`, `postTaskResult()` |
|
||||
| `database/dao/EventDAO.h/cpp` | CRUD для таблицы `events` |
|
||||
| `models/db/EventInfo.h` | Модель задачи |
|
||||
|
||||
---
|
||||
|
||||
## Жизненный цикл задачи
|
||||
|
||||
```
|
||||
Wait → InProgress → Complete
|
||||
↘ Error
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Схема работы
|
||||
|
||||
### 1. Получение задач (каждые 5 секунд)
|
||||
|
||||
```
|
||||
QTimer (5s)
|
||||
└─→ NetworkService::getTasks()
|
||||
└─→ GET /api/v1/task/get_tasks (Bearer token)
|
||||
└─→ signal tasksReceived(QJsonArray)
|
||||
└─→ EventHandler::onTasksReceived()
|
||||
```
|
||||
|
||||
Формат ответа API:
|
||||
|
||||
```json
|
||||
{
|
||||
"success": true,
|
||||
"data": [
|
||||
{ "bank_name": "ozon", "material_id": "c340cd2e-cefd-48d6-a813-c4be097ae493", "type": "FETCH_PROFILE", "body": {} },
|
||||
{ "bank_name": "birbank", "material_id": "a1b2c3d4-0000-0000-0000-000000000001", "type": "FETCH_TRANSACTIONS", "body": {} },
|
||||
{ "bank_name": "ozon", "material_id": "a1b2c3d4-0000-0000-0000-000000000002", "type": "CREATE_TRANSACTION", "body": { "amount": 1000, "currency": 643 } }
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
> `material_id` — UUID строка. Поддерживается также числовой формат для совместимости (парсится через `task["material_id"].isString() ? toString() : QString::number(toInt())`).
|
||||
|
||||
### 2. Обработка задачи (`onTasksReceived`)
|
||||
|
||||
```
|
||||
Для каждой задачи из API:
|
||||
├─ existsActiveByExternalId(material_id)?
|
||||
│ YES → пропустить (дедупликация)
|
||||
│ NO → продолжить
|
||||
│
|
||||
├─ AccountDAO::getAccountsByAppCode(bank_name)
|
||||
│ найти аккаунт где materialId == material_id
|
||||
│ НЕ НАЙДЕН → sendTaskResult(status=error) и пропустить
|
||||
│
|
||||
└─ EventDAO::insertEvent(status=Wait)
|
||||
```
|
||||
|
||||
Маппинг типов задач:
|
||||
|
||||
| Тип в API | `EventType` в коде | Скрипт |
|
||||
|-----------|--------------------|--------|
|
||||
| `FETCH_PROFILE` | `FetchProfile` | `Ozon::LoginAndCheckAccountsScript` |
|
||||
| `FETCH_TRANSACTIONS` | `FetchTransactions` | `GetLastDaysHistoryBirbankScript` |
|
||||
| `CREATE_TRANSACTION` | `CreateTransaction` | *(не реализован)* |
|
||||
|
||||
### 3. Запуск задач (тот же `onTasksReceived`)
|
||||
|
||||
```
|
||||
Для каждого онлайн устройства:
|
||||
├─ m_threads[deviceId] запущен? → пропустить
|
||||
│ (одно устройство = один поток одновременно)
|
||||
│
|
||||
├─ EventDAO::getFirstWaitEventByDeviceId(deviceId)
|
||||
│ самая старая Wait-задача (ORDER BY timestamp ASC)
|
||||
│
|
||||
├─ ApplicationDAO::getApplication() → статус == "active"?
|
||||
│
|
||||
├─ EventDAO::updateEvent(id, InProgress)
|
||||
│
|
||||
└─ startThreadForTask(account, event)
|
||||
```
|
||||
|
||||
### 4. Выполнение скрипта (QThread)
|
||||
|
||||
```
|
||||
QThread
|
||||
└─→ Script::start()
|
||||
├─ ADB команды (adb shell, screenshot, xml dump)
|
||||
├─ ScreenXmlParser (парсинг UI)
|
||||
├─ Tesseract OCR (при необходимости)
|
||||
└─ emit finishedWithResult(error или "")
|
||||
└─→ EventHandler::updateEventThread()
|
||||
```
|
||||
|
||||
> **Watchdog:** если скрипт не завершился за **5 минут** — поток принудительно завершается.
|
||||
|
||||
### 5. Завершение задачи (`updateEventThread`)
|
||||
|
||||
```
|
||||
result == "" → EventDAO::updateEvent(Complete)
|
||||
result != "" → EventDAO::updateEvent(Error, message)
|
||||
|
||||
└─→ sendEventStatus() (новый QThread)
|
||||
└─→ POST /api/v1/event/status ⚠️ устаревший эндпоинт, требует замены на task_result
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Отправка результата задачи
|
||||
|
||||
Используется `POST /api/v1/task/task_result` через `NetworkService::postTaskResult()`.
|
||||
|
||||
### При ненайденном аккаунте (material_id не в БД)
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_PROFILE",
|
||||
"material_id": 42,
|
||||
"bank_name": "ozon",
|
||||
"data": {
|
||||
"status": "error",
|
||||
"description": "Account not found for material_id: 42"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### При успешном выполнении — FETCH_PROFILE
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_PROFILE",
|
||||
"material_id": 42,
|
||||
"bank_name": "ozon",
|
||||
"data": {
|
||||
"bank_account_id": "string",
|
||||
"currency": 643,
|
||||
"amount": 15000,
|
||||
"extra": {}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### При успешном выполнении — FETCH_TRANSACTIONS
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_TRANSACTIONS",
|
||||
"material_id": 7,
|
||||
"bank_name": "birbank",
|
||||
"data": [
|
||||
{
|
||||
"bank_transaction_id": "string",
|
||||
"amount": 500,
|
||||
"currency": 944,
|
||||
"transaction_type": "bank",
|
||||
"bank_participator_id": "string",
|
||||
"status": "completed",
|
||||
"extra": {},
|
||||
"created_at": "2024-01-01T12:00:00"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### При ошибке выполнения скрипта
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "FETCH_PROFILE",
|
||||
"material_id": 42,
|
||||
"bank_name": "ozon",
|
||||
"data": {
|
||||
"status": "error",
|
||||
"description": "Cant open the home screen: emulator-5554 ozon"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Модель `EventInfo`
|
||||
|
||||
```cpp
|
||||
struct EventInfo {
|
||||
int id; // локальный ID в SQLite
|
||||
int externalId; // material_id из API
|
||||
int accountId; // FK → accounts
|
||||
QString deviceId; // ADB device id
|
||||
|
||||
double amount;
|
||||
QString phone;
|
||||
QString bankName;
|
||||
QString comment;
|
||||
|
||||
QByteArray screenshot; // последний скриншот экрана
|
||||
|
||||
EventStatus status; // Wait / InProgress / Complete / Error
|
||||
EventType type; // FetchProfile / FetchTransactions / CreateTransaction
|
||||
|
||||
QDateTime timestamp;
|
||||
QDateTime updateTime;
|
||||
QDateTime completeTime;
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Дедупликация
|
||||
|
||||
`EventDAO::existsActiveByExternalId(int externalId)` — проверяет наличие записи
|
||||
с `status IN ('wait', 'iPprogress')` для данного `external_id`.
|
||||
Гарантирует что одна и та же задача не попадёт в очередь дважды
|
||||
пока не завершится предыдущее выполнение.
|
||||
|
||||
---
|
||||
|
||||
## Ограничения
|
||||
|
||||
- **1 поток на устройство** — пока устройство занято, остальные задачи ждут следующего поллинга
|
||||
- **Очерёдность** — внутри устройства задачи выполняются по `timestamp ASC`
|
||||
- **Интервал поллинга** — 5 секунд (`m_pollTimer->setInterval`)
|
||||
- **Пути API** — настраиваются в `config.ini`:
|
||||
- `net/pathGetTasks` (дефолт `/api/v1/task/get_tasks`)
|
||||
- `net/pathTaskResult` (дефолт `/api/v1/task/task_result`)
|
||||
42
docs/index.md
Normal file
@ -0,0 +1,42 @@
|
||||
# ARCDeskProject — Документация
|
||||
|
||||
Десктопное Qt6/C++ приложение для мониторинга и автоматизации Android банковских приложений через ADB.
|
||||
|
||||
---
|
||||
|
||||
## Оглавление
|
||||
|
||||
### Архитектура приложения
|
||||
|
||||
| Раздел | Описание |
|
||||
|--------|----------|
|
||||
| [События и задачи](events.md) | Очередь задач: получение из API, хранение в БД, выполнение скриптов |
|
||||
|
||||
### API Backend (`Adb Core Backend v1.0.0`)
|
||||
|
||||
| Раздел | Описание |
|
||||
|--------|----------|
|
||||
| [Авторизация](api/auth.md) | Логин, refresh token, хранение токенов |
|
||||
| [Profile & Material](api/profile.md) | Банковские профили и карты (материалы) |
|
||||
| [Device & Desktop](api/device.md) | Регистрация десктопов и Android-устройств, скриншоты |
|
||||
| [Task](api/task.md) | Получение задач, отправка результатов |
|
||||
|
||||
---
|
||||
|
||||
## Общая архитектура приложения
|
||||
|
||||
```
|
||||
MainWindow (Qt6 GUI)
|
||||
├── DeviceScreener (QThread) — опрос ADB устройств, скриншоты
|
||||
├── EventHandler (QThread) — обработка задач, запуск скриптов
|
||||
└── NetworkService (QThread) — синхронизация с REST API (localhost:9999/api/v1/)
|
||||
|
||||
Скрипты: android/{rshb,birbank,toss,ozon,ziraat}/
|
||||
Database: SQLite — DatabaseManager (singleton) + DAO-слой
|
||||
```
|
||||
|
||||
## Технологии
|
||||
|
||||
- **Qt6** — Widgets, Network, Sql, Xml
|
||||
- **C++17**, SQLite, ADB, Tesseract OCR
|
||||
- CMake
|
||||
33
main.cpp
@ -18,6 +18,7 @@
|
||||
|
||||
#include "EventHandler.h"
|
||||
#include "net/NetworkService.h"
|
||||
#include "ozon/PayByPhoneScript.h"
|
||||
#include "widget/loader/ErrorWindow.h"
|
||||
#include "widget/loader/LoaderWindow.h"
|
||||
#include "widget/loader/RegistrationWindow.h"
|
||||
@ -67,6 +68,35 @@ void startEventHandler(const QCoreApplication &app) {
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void startPayByPhoneTest() {
|
||||
// Берём первый девайс с ozon аккаунтом
|
||||
QList<AccountInfo> accounts = AccountDAO::getAccountsByAppCode("ozon");
|
||||
if (accounts.isEmpty()) {
|
||||
qWarning() << "[PayByPhoneTest] No ozon accounts found";
|
||||
return;
|
||||
}
|
||||
AccountInfo account = accounts.first();
|
||||
qDebug() << "[PayByPhoneTest] Using account:" << account.id << account.deviceId << account.lastNumbers;
|
||||
|
||||
auto *thread = new QThread(nullptr);
|
||||
auto *worker = new Ozon::PayByPhoneScript(
|
||||
account,
|
||||
"+79855532896",
|
||||
"Альфа-Банк",
|
||||
150,
|
||||
nullptr
|
||||
);
|
||||
worker->moveToThread(thread);
|
||||
QObject::connect(thread, &QThread::started, worker, &Ozon::PayByPhoneScript::start);
|
||||
QObject::connect(worker, &Ozon::PayByPhoneScript::finished, thread, &QThread::quit);
|
||||
QObject::connect(worker, &Ozon::PayByPhoneScript::finished, worker, &QObject::deleteLater);
|
||||
QObject::connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
QObject::connect(worker, &Ozon::PayByPhoneScript::finishedWithResult, [](const QString &result) {
|
||||
qDebug() << "[PayByPhoneTest] Result:" << result;
|
||||
});
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void startNetworkService(const QCoreApplication &app) {
|
||||
auto *paymentThread = new QThread;
|
||||
auto *networkService = new NetworkService;
|
||||
@ -179,6 +209,7 @@ int main(int argc, char *argv[]) {
|
||||
startNetworkService(app);
|
||||
startDeviceScreener(app);
|
||||
startEventHandler(app);
|
||||
startPayByPhoneTest();
|
||||
}, Qt::QueuedConnection);
|
||||
};
|
||||
|
||||
@ -206,6 +237,7 @@ int main(int argc, char *argv[]) {
|
||||
startNetworkService(app);
|
||||
startDeviceScreener(app);
|
||||
startEventHandler(app);
|
||||
startPayByPhoneTest();
|
||||
} else if (r == 0) {
|
||||
QSettings s("config.ini", QSettings::IniFormat);
|
||||
s.remove("common/api_key");
|
||||
@ -226,6 +258,7 @@ int main(int argc, char *argv[]) {
|
||||
startNetworkService(app);
|
||||
startDeviceScreener(app);
|
||||
startEventHandler(app);
|
||||
startPayByPhoneTest();
|
||||
} else if (result == 0) {
|
||||
QSettings s("config.ini", QSettings::IniFormat);
|
||||
s.remove("common/api_key");
|
||||
|
||||
10
models/db/BankInfo.h
Normal file
@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
|
||||
struct BankInfo {
|
||||
int id = -1;
|
||||
QString alias;
|
||||
QString name;
|
||||
QString nspkName;
|
||||
};
|
||||
@ -12,11 +12,9 @@ enum class EventStatus {
|
||||
};
|
||||
|
||||
enum class EventType {
|
||||
Phone,
|
||||
Card,
|
||||
Balance,
|
||||
History,
|
||||
FetchProfile,
|
||||
FetchTransactions,
|
||||
CreateTransaction,
|
||||
Unknown
|
||||
};
|
||||
|
||||
@ -41,27 +39,23 @@ inline EventStatus eventStatusFromString(const QString &str) {
|
||||
|
||||
inline QString eventTypeToString(const EventType type) {
|
||||
switch (type) {
|
||||
case EventType::Phone: return "phone";
|
||||
case EventType::Card: return "card";
|
||||
case EventType::Balance: return "balance";
|
||||
case EventType::History: return "history";
|
||||
case EventType::FetchProfile: return "fetch_profile";
|
||||
case EventType::FetchProfile: return "FETCH_PROFILE";
|
||||
case EventType::FetchTransactions: return "FETCH_TRANSACTIONS";
|
||||
case EventType::CreateTransaction: return "CREATE_TRANSACTION";
|
||||
default: return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
inline EventType eventTypeFromString(const QString &str) {
|
||||
if (str == "phone") return EventType::Phone;
|
||||
if (str == "card") return EventType::Card;
|
||||
if (str == "balance") return EventType::Balance;
|
||||
if (str == "history") return EventType::History;
|
||||
if (str == "fetch_profile") return EventType::FetchProfile;
|
||||
if (str == "FETCH_PROFILE") return EventType::FetchProfile;
|
||||
if (str == "FETCH_TRANSACTIONS") return EventType::FetchTransactions;
|
||||
if (str == "CREATE_TRANSACTION") return EventType::CreateTransaction;
|
||||
return EventType::Unknown;
|
||||
}
|
||||
|
||||
struct EventInfo {
|
||||
int id = -1;
|
||||
int externalId = -1;
|
||||
QString externalId;
|
||||
int accountId = -1;
|
||||
QString deviceId;
|
||||
|
||||
@ -73,6 +67,8 @@ struct EventInfo {
|
||||
EventStatus status = EventStatus::Unknown;
|
||||
EventType type = EventType::Unknown;
|
||||
|
||||
QByteArray screenshot;
|
||||
|
||||
QDateTime updateTime;
|
||||
QDateTime completeTime;
|
||||
QDateTime timestamp;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <QString>
|
||||
#include <QByteArray>
|
||||
#include <QDateTime>
|
||||
|
||||
enum class TransactionType {
|
||||
@ -69,6 +70,8 @@ struct TransactionInfo {
|
||||
QDateTime updateTime;
|
||||
QDateTime completeTime;
|
||||
QDateTime timestamp;
|
||||
|
||||
QByteArray receiptImage;
|
||||
};
|
||||
|
||||
inline QString convertTransactionToString(const TransactionInfo &tx) {
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
#include "EventHandler.h"
|
||||
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QJsonArray>
|
||||
#include <QMap>
|
||||
#include <QThread>
|
||||
#include <QTimer>
|
||||
@ -12,48 +14,124 @@
|
||||
#include "db/ApplicationInfo.h"
|
||||
#include "net/NetworkService.h"
|
||||
#include "birbank/GetLastDaysHistoryBirbankScript.h"
|
||||
#include "birbank/LoginAndCheckAccountsBirbankScript.h"
|
||||
#include "ozon/GetLastTransactionsScript.h"
|
||||
#include "ozon/LoginAndCheckAccountsScript.h"
|
||||
#include "rshb/CommonScript.h"
|
||||
#include "rshb/GetLastDaysHistoryScript.h"
|
||||
#include "rshb/LoginAndCheckAccountsScript.h"
|
||||
#include "rshb/PayByPhoneScript.h"
|
||||
#include "toss/GetLastDaysHistoryTossScript.h"
|
||||
#include "toss/LoginAndCheckAccountsTossScript.h"
|
||||
#include "ziraat/GetLastDaysHistoryZiraatScript.h"
|
||||
#include "ziraat/LoginAndCheckAccountsZiraatScript.h"
|
||||
|
||||
EventHandler::EventHandler(QObject *parent) : QObject(parent) {
|
||||
m_checkTimer = new QTimer(this);
|
||||
m_checkTimer->setInterval(1000);
|
||||
connect(m_checkTimer, &QTimer::timeout, this, &EventHandler::checkAndRestart);
|
||||
}
|
||||
static void sendTaskResult(const QString &type, const QString &materialId, const QString &bankName, const QJsonObject &data);
|
||||
|
||||
EventHandler::EventHandler(QObject *parent) : QObject(parent) {}
|
||||
|
||||
EventHandler::~EventHandler() = default;
|
||||
|
||||
void EventHandler::start() {
|
||||
m_devices = DeviceDAO::getOnlineDeviceCodes();
|
||||
m_network = new NetworkService(this);
|
||||
connect(m_network, &NetworkService::tasksReceived, this, &EventHandler::onTasksReceived);
|
||||
|
||||
clearAll();
|
||||
// Запустим сразу потоки для всех ключей
|
||||
for (auto &key: m_devices) {
|
||||
startThreadForKey(key);
|
||||
}
|
||||
m_pollTimer = new QTimer(this);
|
||||
m_pollTimer->setInterval(5000);
|
||||
connect(m_pollTimer, &QTimer::timeout, m_network, &NetworkService::getTasks);
|
||||
m_pollTimer->start();
|
||||
|
||||
m_checkTimer->start();
|
||||
// Первый запрос сразу при старте
|
||||
m_network->getTasks();
|
||||
}
|
||||
|
||||
void EventHandler::checkAndRestart() {
|
||||
if (!m_running) {
|
||||
return;
|
||||
}
|
||||
m_devices = DeviceDAO::getOnlineDeviceCodes();
|
||||
void EventHandler::onTasksReceived(const QJsonArray &tasks) {
|
||||
if (!m_running) return;
|
||||
|
||||
for (const auto &key: m_devices) {
|
||||
const QThread *thr = m_threads.value(key, nullptr);
|
||||
if (!thr || !thr->isRunning()) {
|
||||
startThreadForKey(key);
|
||||
// 1. Сохраняем новые задачи в БД
|
||||
for (const auto &taskVal : tasks) {
|
||||
const QJsonObject task = taskVal.toObject();
|
||||
const QString bankName = task["bank_name"].toString();
|
||||
const QString materialId = task["material_id"].isString()
|
||||
? task["material_id"].toString()
|
||||
: QString::number(task["material_id"].toInt());
|
||||
const QString typeStr = task["type"].toString();
|
||||
|
||||
const EventType eventType = eventTypeFromString(typeStr);
|
||||
|
||||
if (eventType == EventType::Unknown) {
|
||||
qWarning() << "[EventHandler] Unknown task type:" << typeStr;
|
||||
continue;
|
||||
}
|
||||
|
||||
// Пропускаем если уже есть активная запись с таким external_id
|
||||
if (EventDAO::existsActiveByExternalId(materialId)) continue;
|
||||
|
||||
// Ищем аккаунт по material_id напрямую
|
||||
const AccountInfo account = AccountDAO::findByMaterialId(materialId);
|
||||
if (account.id == -1) {
|
||||
qWarning() << "[EventHandler] Account not found for bank:" << bankName << "material_id:" << materialId;
|
||||
QJsonObject data;
|
||||
data["status"] = "error";
|
||||
data["description"] = "Account not found for material_id: " + materialId;
|
||||
sendTaskResult(typeStr, materialId, bankName, data);
|
||||
continue;
|
||||
}
|
||||
|
||||
EventInfo event;
|
||||
event.status = EventStatus::Wait;
|
||||
event.type = eventType;
|
||||
event.externalId = materialId;
|
||||
event.accountId = account.id;
|
||||
event.deviceId = account.deviceId;
|
||||
event.bankName = bankName;
|
||||
event.timestamp = QDateTime::currentDateTimeUtc();
|
||||
|
||||
if (EventDAO::insertEvent(event) == -1) {
|
||||
qCritical() << "[EventHandler] Failed to insert event, external_id:" << materialId;
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Для каждого онлайн устройства без активного потока — запускаем следующую задачу
|
||||
const QStringList onlineDevices = DeviceDAO::getOnlineDeviceCodes();
|
||||
for (const QString &deviceId : onlineDevices) {
|
||||
if (const QThread *thr = m_threads.value(deviceId, nullptr); thr && thr->isRunning()) continue;
|
||||
|
||||
const EventInfo event = EventDAO::getFirstWaitEventByDeviceId(deviceId);
|
||||
if (event.id == -1) continue;
|
||||
|
||||
const AccountInfo account = AccountDAO::getAccountById(event.accountId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(deviceId, account.appCode);
|
||||
if (app.status != "active") continue;
|
||||
|
||||
if (!EventDAO::updateEvent(event.id, EventStatus::InProgress, "")) {
|
||||
qCritical() << "[EventHandler] Failed to set InProgress, event id:" << event.id;
|
||||
continue;
|
||||
}
|
||||
|
||||
startThreadForTask(account, event);
|
||||
}
|
||||
}
|
||||
|
||||
void sendEventStatus(const QString &deviceId, const int externalId, const int eventId, const QString &status,
|
||||
const QString &error) {
|
||||
static void sendTaskResult(const QString &type, const QString &materialId, const QString &bankName,
|
||||
const QJsonObject &data) {
|
||||
QJsonObject obj;
|
||||
obj["type"] = type;
|
||||
obj["material_id"] = materialId; // UUID string
|
||||
obj["bank_name"] = bankName;
|
||||
obj["data"] = data;
|
||||
|
||||
auto *thread = new QThread;
|
||||
auto *networkService = new NetworkService;
|
||||
networkService->moveToThread(thread);
|
||||
networkService->setPayload(QJsonDocument(obj).toJson());
|
||||
QObject::connect(thread, &QThread::started, networkService, &NetworkService::postTaskResult);
|
||||
QObject::connect(networkService, &NetworkService::finished, thread, &QThread::quit);
|
||||
QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
|
||||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
static void sendEventStatus(const QString &deviceId, const int externalId, const int eventId,
|
||||
const QString &status, const QString &error) {
|
||||
QJsonObject obj;
|
||||
obj["id"] = externalId;
|
||||
obj["externalId"] = eventId;
|
||||
@ -73,31 +151,19 @@ void sendEventStatus(const QString &deviceId, const int externalId, const int ev
|
||||
}
|
||||
|
||||
void EventHandler::updateEventThread(const QString &key, const EventInfo &event, const QString &result) {
|
||||
if (!result.isEmpty()) {
|
||||
if (!EventDAO::updateEvent(event.id, EventStatus::Error, result)) {
|
||||
qCritical() << "Cant update event: " << event.id;
|
||||
const EventStatus newStatus = result.isEmpty() ? EventStatus::Complete : EventStatus::Error;
|
||||
|
||||
if (!EventDAO::updateEvent(event.id, newStatus, result)) {
|
||||
qCritical() << "[EventHandler] Failed to update event:" << event.id;
|
||||
} else {
|
||||
sendEventStatus(
|
||||
event.deviceId,
|
||||
event.externalId,
|
||||
event.id,
|
||||
eventStatusToString(EventStatus::Error),
|
||||
eventStatusToString(newStatus),
|
||||
result
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (!EventDAO::updateEvent(event.id, EventStatus::Complete, "")) {
|
||||
qCritical() << "Cant update event: " << event.id;
|
||||
} else {
|
||||
sendEventStatus(
|
||||
event.deviceId,
|
||||
event.externalId,
|
||||
event.id,
|
||||
eventStatusToString(EventStatus::Complete),
|
||||
""
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto tm = m_timers.take(key)) {
|
||||
tm->stop();
|
||||
@ -106,99 +172,59 @@ void EventHandler::updateEventThread(const QString &key, const EventInfo &event,
|
||||
m_threads.remove(key);
|
||||
}
|
||||
|
||||
void EventHandler::startThreadForKey(const QString &key) {
|
||||
const EventInfo event = EventDAO::getFirstWaitEventByDeviceId(key);
|
||||
if (event.id == -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const AccountInfo account = AccountDAO::getAccountById(event.accountId);
|
||||
const ApplicationInfo app = ApplicationDAO::getApplication(event.deviceId, account.appCode);
|
||||
if (app.status != "active") {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EventDAO::updateEvent(event.id, EventStatus::InProgress, "")) {
|
||||
qCritical() << "Cant update event: " << event.id;
|
||||
return;
|
||||
}
|
||||
|
||||
void EventHandler::startThreadForTask(const AccountInfo &account, const EventInfo &event) {
|
||||
const auto thr = new QThread(this);
|
||||
// qDebug() << "Start thread for key" << eventTypeToString(event.type);
|
||||
if (event.type == EventType::History) {
|
||||
if ("birbank" == account.appCode) {
|
||||
auto *worker = new GetLastDaysHistoryBirbankScript(account);
|
||||
const QString key = account.deviceId;
|
||||
|
||||
// Generic helper: подключает сигналы и переносит worker в поток
|
||||
auto setup = [&](auto *worker) {
|
||||
using W = std::remove_pointer_t<decltype(worker)>;
|
||||
worker->moveToThread(thr);
|
||||
|
||||
connect(thr, &QThread::started, worker, &GetLastDaysHistoryBirbankScript::start);
|
||||
connect(worker, &GetLastDaysHistoryBirbankScript::finished, thr, &QThread::quit);
|
||||
connect(worker, &GetLastDaysHistoryBirbankScript::finished, worker, &QObject::deleteLater);
|
||||
connect(thr, &QThread::started, worker, &W::start);
|
||||
connect(worker, &W::finished, thr, &QThread::quit);
|
||||
connect(worker, &W::finished, worker, &QObject::deleteLater);
|
||||
connect(thr, &QThread::finished, thr, &QObject::deleteLater);
|
||||
|
||||
connect(worker, &GetLastDaysHistoryBirbankScript::finishedWithResult, this,
|
||||
connect(worker, &W::finishedWithResult, this,
|
||||
[this, key, event](const QString &result) {
|
||||
updateEventThread(key, event, result);
|
||||
}
|
||||
);
|
||||
}
|
||||
// auto *worker = new Rshb::GetLastDaysHistoryScript(account);
|
||||
// worker->moveToThread(thr);
|
||||
//
|
||||
// connect(thr, &QThread::started, worker, &Rshb::GetLastDaysHistoryScript::start);
|
||||
// connect(worker, &Rshb::GetLastDaysHistoryScript::finished, thr, &QThread::quit);
|
||||
// connect(worker, &Rshb::GetLastDaysHistoryScript::finished, worker, &QObject::deleteLater);
|
||||
// connect(thr, &QThread::finished, thr, &QObject::deleteLater);
|
||||
//
|
||||
// connect(worker, &Rshb::GetLastDaysHistoryScript::finishedWithResult, this,
|
||||
// [this, key, event](const QString &result) {
|
||||
// updateEventThread(key, event, result);
|
||||
// }
|
||||
// );
|
||||
}
|
||||
};
|
||||
|
||||
const QString &appCode = account.appCode;
|
||||
const QString &deviceId = account.deviceId;
|
||||
|
||||
if (event.type == EventType::FetchProfile) {
|
||||
if ("ozon" == account.appCode) {
|
||||
auto *worker = new Ozon::LoginAndCheckAccountsScript(event.deviceId, account.appCode);
|
||||
worker->moveToThread(thr);
|
||||
|
||||
connect(thr, &QThread::started, worker, &Ozon::LoginAndCheckAccountsScript::start);
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, thr, &QThread::quit);
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finished, worker, &QObject::deleteLater);
|
||||
connect(thr, &QThread::finished, thr, &QObject::deleteLater);
|
||||
|
||||
connect(worker, &Ozon::LoginAndCheckAccountsScript::finishedWithResult, this,
|
||||
[this, key, event](const QString &result) {
|
||||
updateEventThread(key, event, result);
|
||||
}
|
||||
);
|
||||
}
|
||||
if (appCode == "ozon") setup(new Ozon::LoginAndCheckAccountsScript(deviceId, appCode));
|
||||
else if (appCode == "birbank") setup(new LoginAndCheckAccountsBirbankScript(deviceId, appCode));
|
||||
else if (appCode == "rshb") setup(new Rshb::LoginAndCheckAccountsScript(deviceId, appCode));
|
||||
else if (appCode == "toss") setup(new LoginAndCheckAccountsTossScript(deviceId, appCode));
|
||||
else if (appCode == "ziraat") setup(new LoginAndCheckAccountsZiraatScript(deviceId, appCode));
|
||||
else qWarning() << "[EventHandler] FETCH_PROFILE: unknown appCode:" << appCode;
|
||||
}
|
||||
|
||||
if (event.type == EventType::Phone) {
|
||||
// auto *worker = new Rshb::PayByPhoneScript(account, event.phone, event.bankName, event.amount);
|
||||
// worker->moveToThread(thr);
|
||||
//
|
||||
// connect(thr, &QThread::started, worker, &Rshb::PayByPhoneScript::start);
|
||||
// connect(worker, &Rshb::PayByPhoneScript::finished, thr, &QThread::quit);
|
||||
// connect(worker, &Rshb::PayByPhoneScript::finished, worker, &QObject::deleteLater);
|
||||
// connect(thr, &QThread::finished, thr, &QObject::deleteLater);
|
||||
//
|
||||
// connect(worker, &Rshb::PayByPhoneScript::finishedWithResult, this,
|
||||
// [this, key, event](const QString &result) {
|
||||
// updateEventThread(key, event, result);
|
||||
// }
|
||||
// );
|
||||
if (event.type == EventType::FetchTransactions) {
|
||||
if (appCode == "birbank") setup(new GetLastDaysHistoryBirbankScript(account));
|
||||
else if (appCode == "ozon") setup(new Ozon::GetLastTransactionsScript(deviceId, appCode));
|
||||
else if (appCode == "rshb") setup(new Rshb::GetLastDaysHistoryScript(account));
|
||||
else if (appCode == "toss") setup(new GetLastDaysHistoryTossScript(account));
|
||||
else if (appCode == "ziraat") setup(new GetLastDaysHistoryZiraatScript(account));
|
||||
else qWarning() << "[EventHandler] FETCH_TRANSACTIONS: unknown appCode:" << appCode;
|
||||
}
|
||||
|
||||
// Таймер на 5 минут — если не успел finish(), убить и перезапустить
|
||||
if (event.type == EventType::CreateTransaction) {
|
||||
// TODO: реализовать CREATE_TRANSACTION
|
||||
}
|
||||
|
||||
// Watchdog-таймер: 5 минут — убиваем зависший поток
|
||||
const auto timer = new QTimer(this);
|
||||
timer->setSingleShot(true);
|
||||
connect(timer, &QTimer::timeout, this, [this, key]() {
|
||||
qWarning() << "Timeout for key" << key << "- killing stuck thread.";
|
||||
qWarning() << "[EventHandler] Timeout for key" << key << "- killing stuck thread.";
|
||||
if (QThread *thread = m_threads.value(key); thread && thread->isRunning()) {
|
||||
thread->requestInterruption(); // мягкое прерывание
|
||||
thread->requestInterruption();
|
||||
if (!thread->wait(500)) {
|
||||
qWarning() << "Hard terminate for key" << key;
|
||||
qWarning() << "[EventHandler] Hard terminate for key" << key;
|
||||
thread->terminate();
|
||||
thread->wait();
|
||||
}
|
||||
@ -209,7 +235,6 @@ void EventHandler::startThreadForKey(const QString &key) {
|
||||
m_threads.remove(key);
|
||||
});
|
||||
|
||||
// Сохраняем и запускаем
|
||||
m_threads[key] = thr;
|
||||
m_timers[key] = timer;
|
||||
|
||||
@ -218,8 +243,7 @@ void EventHandler::startThreadForKey(const QString &key) {
|
||||
}
|
||||
|
||||
void EventHandler::clearAll() {
|
||||
// Останавливаем и удаляем старые
|
||||
for (QThread *thread: m_threads) {
|
||||
for (QThread *thread : m_threads) {
|
||||
if (thread->isRunning()) {
|
||||
thread->requestInterruption();
|
||||
thread->terminate();
|
||||
@ -229,7 +253,7 @@ void EventHandler::clearAll() {
|
||||
}
|
||||
m_threads.clear();
|
||||
|
||||
for (QTimer *timer: m_timers) {
|
||||
for (QTimer *timer : m_timers) {
|
||||
timer->stop();
|
||||
timer->deleteLater();
|
||||
}
|
||||
@ -238,6 +262,6 @@ void EventHandler::clearAll() {
|
||||
|
||||
void EventHandler::stop() {
|
||||
m_running = false;
|
||||
m_checkTimer->stop();
|
||||
if (m_pollTimer) m_pollTimer->stop();
|
||||
clearAll();
|
||||
}
|
||||
|
||||
@ -1,10 +1,14 @@
|
||||
#pragma once
|
||||
#include <QJsonArray>
|
||||
#include <QMap>
|
||||
#include <QObject>
|
||||
#include <QTimer>
|
||||
|
||||
#include "db/AccountInfo.h"
|
||||
#include "db/EventInfo.h"
|
||||
|
||||
class NetworkService;
|
||||
|
||||
class EventHandler final : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
@ -22,19 +26,18 @@ signals:
|
||||
void finished();
|
||||
|
||||
private slots:
|
||||
void checkAndRestart(); // вызывается по таймеру
|
||||
void onTasksReceived(const QJsonArray &tasks);
|
||||
|
||||
private:
|
||||
bool m_running = true;
|
||||
QMap<QString, QThread *> m_threads;
|
||||
QMap<QString, QTimer *> m_timers;
|
||||
QTimer *m_checkTimer = nullptr;
|
||||
|
||||
QStringList m_devices;
|
||||
QTimer *m_pollTimer = nullptr;
|
||||
NetworkService *m_network = nullptr;
|
||||
|
||||
void clearAll();
|
||||
|
||||
void updateEventThread(const QString &key, const EventInfo &event, const QString &result);
|
||||
|
||||
void startThreadForKey(const QString &key);
|
||||
void startThreadForTask(const AccountInfo &account, const EventInfo &event);
|
||||
};
|
||||
|
||||
@ -33,6 +33,8 @@ NetworkService::NetworkService(QObject *parent) : QObject(parent) {
|
||||
m_pathDeviceScreenshot= settings.value("net/pathDeviceScreenshot","/api/v1/device/add_screenshot/").toString();
|
||||
m_pathBankProfile = settings.value("net/pathBankProfile", "/api/v1/profile/bank_profile").toString();
|
||||
m_pathMaterial = settings.value("net/pathMaterial", "/api/v1/profile/material").toString();
|
||||
m_pathGetTasks = settings.value("net/pathGetTasks", "/api/v1/task/get_tasks").toString();
|
||||
m_pathTaskResult = settings.value("net/pathTaskResult", "/api/v1/task/task_result").toString();
|
||||
m_manager = new QNetworkAccessManager(this);
|
||||
m_token = settings.value("common/token").toString();
|
||||
m_accessToken = settings.value("common/access_token").toString();
|
||||
@ -586,14 +588,60 @@ void NetworkService::scheduleNextRefresh() {
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkService::getTasks() {
|
||||
if (m_fetchingTasks) return;
|
||||
m_fetchingTasks = true;
|
||||
|
||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||
const QString accessToken = settings.value("common/access_token").toString();
|
||||
|
||||
QUrl url(m_apiBase + m_pathGetTasks);
|
||||
QNetworkRequest request(url);
|
||||
request.setRawHeader("Authorization", ("Bearer " + accessToken).toUtf8());
|
||||
|
||||
QNetworkReply *reply = m_manager->get(request);
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply]() {
|
||||
m_fetchingTasks = false;
|
||||
reply->deleteLater();
|
||||
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
qWarning() << "[NetworkService] getTasks error:" << reply->errorString();
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray raw = reply->readAll();
|
||||
QJsonParseError err;
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(raw, &err);
|
||||
if (err.error != QJsonParseError::NoError || !doc.isObject()) {
|
||||
qWarning() << "[NetworkService] getTasks: invalid JSON";
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject root = doc.object();
|
||||
if (!root.value("success").toBool()) {
|
||||
qWarning() << "[NetworkService] getTasks: success=false" << root;
|
||||
return;
|
||||
}
|
||||
|
||||
emit tasksReceived(root.value("data").toArray());
|
||||
});
|
||||
}
|
||||
|
||||
void NetworkService::postTaskResult() {
|
||||
const QJsonObject body = QJsonDocument::fromJson(m_json).object();
|
||||
const QJsonValue result = postJson(m_apiBase + m_pathTaskResult, body, true);
|
||||
if (result.isNull() || result.isUndefined()) {
|
||||
qWarning() << "[NetworkService] postTaskResult failed, body:" << QJsonDocument(body).toJson(QJsonDocument::Compact);
|
||||
}
|
||||
emit finished();
|
||||
}
|
||||
|
||||
void NetworkService::start() {
|
||||
m_running = true;
|
||||
|
||||
// Проверяем токен при старте и планируем рефреш по времени жизни
|
||||
ensureValidToken();
|
||||
scheduleNextRefresh();
|
||||
|
||||
// TODO: GET /api/v1/task/get_tasks (polling)
|
||||
}
|
||||
|
||||
void NetworkService::stop() {
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
#include <qhttpmultipart.h>
|
||||
#include <QJsonArray>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QObject>
|
||||
#include <QMap>
|
||||
@ -47,6 +48,10 @@ public slots:
|
||||
|
||||
void updateDevice();
|
||||
|
||||
void getTasks();
|
||||
|
||||
void postTaskResult();
|
||||
|
||||
void start();
|
||||
void stop();
|
||||
|
||||
@ -54,6 +59,7 @@ signals:
|
||||
void finished();
|
||||
void finishedWithResult(int value);
|
||||
void tokenRefreshFailed();
|
||||
void tasksReceived(QJsonArray tasks);
|
||||
|
||||
private:
|
||||
QByteArray m_json;
|
||||
@ -78,6 +84,9 @@ private:
|
||||
QString m_pathDeviceScreenshot;
|
||||
QString m_pathBankProfile;
|
||||
QString m_pathMaterial;
|
||||
QString m_pathGetTasks;
|
||||
QString m_pathTaskResult;
|
||||
bool m_fetchingTasks = false;
|
||||
|
||||
QNetworkAccessManager *m_manager;
|
||||
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
#include <QStackedWidget>
|
||||
|
||||
#include "TutorialWindow.h"
|
||||
#include "bank/BankListWindow.h"
|
||||
#include "db/DeviceInfo.h"
|
||||
#include "device/DeviceWidget.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
@ -35,9 +36,11 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
QMenu *menu = menuBar()->addMenu("Меню");
|
||||
auto *devicesPageAction = new QAction("Активные устройства", this);
|
||||
// auto *accountPageAction = new QAction("Аккаунты", this);
|
||||
auto *bankListAction = new QAction("Банки", this);
|
||||
auto *tutorialPageAction = new QAction("Инструкция", this);
|
||||
menu->addAction(devicesPageAction);
|
||||
// menu->addAction(accountPageAction);
|
||||
menu->addAction(bankListAction);
|
||||
menu->addAction(tutorialPageAction);
|
||||
|
||||
connect(devicesPageAction, &QAction::triggered, this, [=]() {
|
||||
@ -52,6 +55,15 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
// deleteDevicePage(); // освобождаем ресурсы
|
||||
// });
|
||||
|
||||
connect(bankListAction, &QAction::triggered, this, [=]() {
|
||||
if (!m_bankListWindow) {
|
||||
m_bankListWindow = new BankListWindow(this);
|
||||
}
|
||||
m_bankListWindow->show();
|
||||
m_bankListWindow->raise();
|
||||
m_bankListWindow->activateWindow();
|
||||
});
|
||||
|
||||
connect(tutorialPageAction, &QAction::triggered, this, [=]() {
|
||||
if (!m_tutorialWindow) {
|
||||
m_tutorialWindow = new TutorialWindow(this);
|
||||
|
||||
@ -26,6 +26,7 @@ private:
|
||||
QWidget *m_devicesWindow = nullptr;
|
||||
QWidget *m_accountsWindow = nullptr;
|
||||
QWidget *m_tutorialWindow = nullptr;
|
||||
QWidget *m_bankListWindow = nullptr;
|
||||
|
||||
void createDevicePage();
|
||||
|
||||
|
||||
@ -15,9 +15,11 @@
|
||||
#include "ozon/LoginAndCheckAccountsScript.h"
|
||||
#include "ozon/GetProfileInfoScript.h"
|
||||
#include "ozon/GetCardInfoScript.h"
|
||||
#include "ozon/GetLastTransactionsScript.h"
|
||||
#include "toss/LoginAndCheckAccountsTossScript.h"
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/ApplicationDAO.h"
|
||||
#include "bank/TransactionWindow.h"
|
||||
#include "device/EditBankAccountDialog.h"
|
||||
#include "device/EditBankDialog.h"
|
||||
#include "net/NetworkService.h"
|
||||
@ -205,8 +207,29 @@ AccountSettingsWindow::AccountSettingsWindow(
|
||||
}
|
||||
});
|
||||
|
||||
QPushButton *transactionsButton = new QPushButton("Проверить транзакции");
|
||||
transactionsButton->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
transactionsButton->setStyleSheet("background-color: #1565C0; color: white;");
|
||||
QObject::connect(transactionsButton, &QPushButton::clicked, [this]() {
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle(tr("Проверить транзакции?"));
|
||||
msgBox.setText(
|
||||
"Запустится скрипт получения последних транзакций из " + m_applicationInfo.name +
|
||||
". Окно с настройками закроется.");
|
||||
QPushButton *runBtn = msgBox.addButton(tr("Запустить"), QMessageBox::AcceptRole);
|
||||
QPushButton *cancelBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole);
|
||||
msgBox.setDefaultButton(cancelBtn);
|
||||
msgBox.setModal(true);
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() == runBtn) {
|
||||
startGetLastTransactions(m_applicationInfo.code);
|
||||
close();
|
||||
}
|
||||
});
|
||||
|
||||
ozonRow->addWidget(profileButton);
|
||||
ozonRow->addWidget(cardButton);
|
||||
ozonRow->addWidget(transactionsButton);
|
||||
ozonRow->addStretch();
|
||||
layout->addLayout(ozonRow);
|
||||
}
|
||||
@ -322,6 +345,19 @@ void AccountSettingsWindow::startGetCardInfo(const QString &appCode) {
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void AccountSettingsWindow::startGetLastTransactions(const QString &appCode) {
|
||||
if (appCode != "ozon") return;
|
||||
|
||||
QThread *thread = new QThread(nullptr);
|
||||
auto *worker = new Ozon::GetLastTransactionsScript(m_deviceId, appCode, nullptr);
|
||||
worker->moveToThread(thread);
|
||||
connect(thread, &QThread::started, worker, &Ozon::GetLastTransactionsScript::start);
|
||||
connect(worker, &Ozon::GetLastTransactionsScript::finished, thread, &QThread::quit);
|
||||
connect(worker, &Ozon::GetLastTransactionsScript::finished, worker, &QObject::deleteLater);
|
||||
connect(thread, &QThread::finished, thread, &QObject::deleteLater);
|
||||
thread->start();
|
||||
}
|
||||
|
||||
void AccountSettingsWindow::sendAppStatus(const QString &deviceId, const QString &code, const QString &status) {
|
||||
Q_UNUSED(deviceId) Q_UNUSED(code) Q_UNUSED(status)
|
||||
// TODO: заменить на новый API
|
||||
@ -356,7 +392,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
|
||||
tableWidget->clearContents();
|
||||
tableWidget->setRowCount(accounts.length());
|
||||
tableWidget->setColumnCount(7);
|
||||
tableWidget->setColumnCount(8);
|
||||
tableWidget->setHorizontalHeaderLabels({
|
||||
"Время обновления",
|
||||
"Номер",
|
||||
@ -364,6 +400,7 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
"Сумма",
|
||||
"Валюта",
|
||||
"Статус",
|
||||
"",
|
||||
""
|
||||
});
|
||||
|
||||
@ -371,7 +408,6 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
tableWidget->setTextElideMode(Qt::ElideNone);
|
||||
tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(5, QHeaderView::ResizeToContents);
|
||||
tableWidget->verticalHeader()->setSectionResizeMode(6, QHeaderView::ResizeToContents);
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
||||
|
||||
|
||||
@ -413,32 +449,35 @@ void AccountSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||
tableWidget->setItem(k, 5, status);
|
||||
|
||||
|
||||
auto *editBankAccountBtn = new QPushButton("Редактировать", tableWidget);
|
||||
tableWidget->setCellWidget(k, 6, editBankAccountBtn);
|
||||
auto *deleteBankAccountBtn = new QPushButton("Удалить", tableWidget);
|
||||
deleteBankAccountBtn->setStyleSheet("background-color: red; color: white;");
|
||||
tableWidget->setCellWidget(k, 6, deleteBankAccountBtn);
|
||||
|
||||
connect(editBankAccountBtn, &QPushButton::clicked, this, [this, tableWidget, account]() {
|
||||
EditBankAccountDialog dlg(account, this);
|
||||
if (dlg.exec() == QDialog::Accepted) {
|
||||
const QString cardNumber = dlg.cardNumberValue();
|
||||
const QString currency = dlg.currencyValue();
|
||||
bool on = dlg.toggled();
|
||||
|
||||
if (!AccountDAO::updateAccountById(account.id, on ? "active" : "off", cardNumber, currency)) {
|
||||
qDebug() << "Cant update bank account data";
|
||||
} else {
|
||||
connect(deleteBankAccountBtn, &QPushButton::clicked, this, [this, tableWidget, account]() {
|
||||
QMessageBox msgBox(this);
|
||||
msgBox.setWindowTitle("Удалить аккаунт?");
|
||||
msgBox.setText("Вы уверены, что хотите удалить аккаунт " + account.lastNumbers + "?");
|
||||
QPushButton *deleteBtn = msgBox.addButton("Удалить", QMessageBox::AcceptRole);
|
||||
QPushButton *cancelBtn = msgBox.addButton("Отмена", QMessageBox::RejectRole);
|
||||
msgBox.setDefaultButton(cancelBtn);
|
||||
msgBox.exec();
|
||||
if (msgBox.clickedButton() == deleteBtn) {
|
||||
if (AccountDAO::deleteAccount(account.id)) {
|
||||
reloadContent(tableWidget);
|
||||
AccountInfo accountInfo;
|
||||
accountInfo.currency = currency;
|
||||
accountInfo.description = cardNumber;
|
||||
accountInfo.amount = account.amount;
|
||||
accountInfo.currency = currency;
|
||||
accountInfo.appCode = m_applicationInfo.code;
|
||||
accountInfo.deviceId = m_deviceId;
|
||||
accountInfo.status = on ? AccountStatus::Active : AccountStatus::Off;
|
||||
accountInfo.lastNumbers = cardNumber.right(4);
|
||||
updateAccountData(accountInfo);
|
||||
tableWidget->resizeColumnsToContents();
|
||||
} else {
|
||||
qWarning() << "Cant delete account" << account.id;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
auto *transactionsBtn = new QPushButton("Транзакции", tableWidget);
|
||||
transactionsBtn->setStyleSheet("background-color: #1565C0; color: white;");
|
||||
tableWidget->setCellWidget(k, 7, transactionsBtn);
|
||||
|
||||
connect(transactionsBtn, &QPushButton::clicked, this, [this, account]() {
|
||||
auto *txWindow = new TransactionWindow(account.id, this);
|
||||
txWindow->show();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -27,6 +27,8 @@ private:
|
||||
|
||||
void startGetCardInfo(const QString &appCode);
|
||||
|
||||
void startGetLastTransactions(const QString &appCode);
|
||||
|
||||
void reloadContent(QTableWidget *tableWidget);
|
||||
|
||||
void sendAppStatus(const QString &deviceId, const QString &code, const QString &status);
|
||||
|
||||
117
views/bank/BankListWindow.cpp
Normal file
@ -0,0 +1,117 @@
|
||||
#include "BankListWindow.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QMessageBox>
|
||||
#include <QNetworkAccessManager>
|
||||
#include <QNetworkReply>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "dao/BankDAO.h"
|
||||
|
||||
BankListWindow::BankListWindow(QWidget *parent)
|
||||
: QWidget(parent,
|
||||
Qt::Window
|
||||
| Qt::WindowTitleHint
|
||||
| Qt::WindowSystemMenuHint
|
||||
| Qt::WindowCloseButtonHint
|
||||
| Qt::WindowMinMaxButtonsHint) {
|
||||
setWindowTitle("Банки");
|
||||
setMinimumSize(600, 400);
|
||||
resize(700, 500);
|
||||
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
|
||||
auto *topRow = new QHBoxLayout();
|
||||
auto *syncBtn = new QPushButton("Синхронизировать");
|
||||
syncBtn->setStyleSheet("background-color: #1565C0; color: white;");
|
||||
syncBtn->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred);
|
||||
topRow->addWidget(syncBtn);
|
||||
topRow->addStretch();
|
||||
layout->addLayout(topRow);
|
||||
|
||||
connect(syncBtn, &QPushButton::clicked, this, &BankListWindow::syncBanks);
|
||||
|
||||
m_tableWidget = new QTableWidget(this);
|
||||
m_tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
m_tableWidget->setSelectionMode(QAbstractItemView::SingleSelection);
|
||||
m_tableWidget->setSelectionBehavior(QAbstractItemView::SelectRows);
|
||||
m_tableWidget->setColumnCount(3);
|
||||
m_tableWidget->setHorizontalHeaderLabels({"Alias", "Название", "NSPK"});
|
||||
m_tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
m_tableWidget->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
|
||||
|
||||
layout->addWidget(m_tableWidget);
|
||||
|
||||
reloadTable();
|
||||
}
|
||||
|
||||
void BankListWindow::reloadTable() {
|
||||
QList<BankInfo> banks = BankDAO::getAll();
|
||||
|
||||
m_tableWidget->setRowCount(banks.size());
|
||||
for (int i = 0; i < banks.size(); ++i) {
|
||||
const BankInfo &b = banks[i];
|
||||
|
||||
auto *alias = new QTableWidgetItem(b.alias);
|
||||
alias->setFlags(alias->flags() & ~Qt::ItemIsEditable);
|
||||
m_tableWidget->setItem(i, 0, alias);
|
||||
|
||||
auto *name = new QTableWidgetItem(b.name);
|
||||
name->setFlags(name->flags() & ~Qt::ItemIsEditable);
|
||||
m_tableWidget->setItem(i, 1, name);
|
||||
|
||||
auto *nspk = new QTableWidgetItem(b.nspkName);
|
||||
nspk->setFlags(nspk->flags() & ~Qt::ItemIsEditable);
|
||||
m_tableWidget->setItem(i, 2, nspk);
|
||||
}
|
||||
}
|
||||
|
||||
void BankListWindow::syncBanks() {
|
||||
auto *manager = new QNetworkAccessManager(this);
|
||||
|
||||
QNetworkRequest request(QUrl("https://api.brtservice.io/api/rest/info/"));
|
||||
QNetworkReply *reply = manager->get(request);
|
||||
|
||||
connect(reply, &QNetworkReply::finished, this, [this, reply, manager]() {
|
||||
if (reply->error() != QNetworkReply::NoError) {
|
||||
QMessageBox::warning(this, "Ошибка", "Не удалось загрузить банки: " + reply->errorString());
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
return;
|
||||
}
|
||||
|
||||
const QByteArray data = reply->readAll();
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(data);
|
||||
|
||||
QJsonArray array;
|
||||
if (doc.isArray()) {
|
||||
array = doc.array();
|
||||
} else if (doc.isObject() && doc.object().contains("data")) {
|
||||
array = doc.object()["data"].toArray();
|
||||
}
|
||||
|
||||
QList<BankInfo> banks;
|
||||
for (const QJsonValue &val : array) {
|
||||
QJsonObject obj = val.toObject();
|
||||
BankInfo bank;
|
||||
bank.alias = obj["alias"].toString();
|
||||
bank.name = obj["name"].toString();
|
||||
bank.nspkName = obj["nspk_name"].toString();
|
||||
if (!bank.alias.isEmpty()) {
|
||||
banks.append(bank);
|
||||
}
|
||||
}
|
||||
|
||||
BankDAO::replaceAll(banks);
|
||||
reloadTable();
|
||||
|
||||
qDebug() << "[BankListWindow] Synced" << banks.size() << "banks";
|
||||
reply->deleteLater();
|
||||
manager->deleteLater();
|
||||
});
|
||||
}
|
||||
17
views/bank/BankListWindow.h
Normal file
@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QTableWidget>
|
||||
|
||||
class BankListWindow final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit BankListWindow(QWidget *parent = nullptr);
|
||||
|
||||
private:
|
||||
QTableWidget *m_tableWidget;
|
||||
|
||||
void reloadTable();
|
||||
void syncBanks();
|
||||
};
|
||||
@ -1,7 +1,10 @@
|
||||
#include "TransactionWindow.h"
|
||||
|
||||
#include <qabstractitemview.h>
|
||||
#include <QDialog>
|
||||
#include <QHeaderView>
|
||||
#include <QPixmap>
|
||||
#include <QPushButton>
|
||||
#include <QVBoxLayout>
|
||||
#include <QLabel>
|
||||
#include <QScrollArea>
|
||||
@ -46,8 +49,7 @@ QTableWidgetItem *createTableWidget(const QString &text) {
|
||||
|
||||
TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDialog(parent) {
|
||||
setWindowTitle("Транзакции");
|
||||
setMinimumSize(1000, 600); // минимальный размер
|
||||
setMaximumSize(1000, 600); // максимальный размер
|
||||
setMinimumSize(800, 600);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
|
||||
@ -101,27 +103,27 @@ TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDia
|
||||
tableWidget->setEditTriggers(QAbstractItemView::NoEditTriggers);
|
||||
|
||||
tableWidget->setRowCount(transactions.length());
|
||||
tableWidget->setColumnCount(6);
|
||||
tableWidget->setColumnCount(7);
|
||||
tableWidget->setHorizontalHeaderLabels({
|
||||
"Дата и время",
|
||||
"Сумма",
|
||||
"Банк",
|
||||
"Телефон",
|
||||
"Статус",
|
||||
"Описание"
|
||||
"Описание",
|
||||
""
|
||||
});
|
||||
tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||
}
|
||||
tableWidget->horizontalHeader()->setSectionResizeMode(5, QHeaderView::Stretch);
|
||||
// tableWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred);
|
||||
|
||||
tableWidget->setItemDelegateForColumn(1, new AmountDelegate(tableWidget));
|
||||
|
||||
for (int k = 0; k < transactions.size(); ++k) {
|
||||
TransactionInfo tr = transactions[k];
|
||||
tableWidget->setItem(k, 0, createTableWidget(tr.timestamp.toString("dd.MM.yyyy HH:mm:ss")));
|
||||
tableWidget->setItem(k, 0, createTableWidget(tr.bankTime));
|
||||
tableWidget->setItem(k, 1, createTableWidget(QString::number(tr.amount)));
|
||||
tableWidget->setItem(k, 2, createTableWidget(tr.bankName));
|
||||
tableWidget->setItem(k, 3, createTableWidget(tr.phone));
|
||||
@ -130,12 +132,42 @@ TransactionWindow::TransactionWindow(const int accountId, QWidget *parent): QDia
|
||||
QTableWidgetItem *item = new QTableWidgetItem(tr.description);
|
||||
item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||
item->setSizeHint(QSize(100, tr.description.length() / 2)); // Минимальная высота ячейки для переноса
|
||||
item->setSizeHint(QSize(100, tr.description.length() / 2));
|
||||
tableWidget->setItem(k, 5, item);
|
||||
|
||||
if (!tr.receiptImage.isEmpty()) {
|
||||
auto *docBtn = new QPushButton("Документы", tableWidget);
|
||||
docBtn->setStyleSheet("background-color: #1565C0; color: white;");
|
||||
tableWidget->setCellWidget(k, 6, docBtn);
|
||||
|
||||
connect(docBtn, &QPushButton::clicked, this, [this, tr]() {
|
||||
QPixmap pixmap;
|
||||
pixmap.loadFromData(tr.receiptImage);
|
||||
if (pixmap.isNull()) return;
|
||||
|
||||
auto *imgDialog = new QDialog(this);
|
||||
imgDialog->setWindowTitle("Документ");
|
||||
imgDialog->setAttribute(Qt::WA_DeleteOnClose);
|
||||
auto *dlgLayout = new QVBoxLayout(imgDialog);
|
||||
auto *imgLabel = new QLabel(imgDialog);
|
||||
imgLabel->setPixmap(pixmap.scaled(600, 1000, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||
dlgLayout->addWidget(imgLabel);
|
||||
imgDialog->resize(imgLabel->pixmap().size());
|
||||
imgDialog->show();
|
||||
});
|
||||
}
|
||||
}
|
||||
layout->addWidget(tableWidget);
|
||||
tableWidget->resizeColumnsToContents();
|
||||
tableWidget->resizeRowsToContents();
|
||||
|
||||
// Подгоняем ширину окна под содержимое таблицы
|
||||
int totalWidth = tableWidget->verticalHeader()->width() + tableWidget->frameWidth() * 2 + 20;
|
||||
for (int i = 0; i < tableWidget->columnCount(); ++i) {
|
||||
totalWidth += tableWidget->columnWidth(i);
|
||||
}
|
||||
resize(qMax(800, totalWidth), 600);
|
||||
|
||||
// for (int row = 0; row < tableWidget->rowCount(); ++row) {
|
||||
// for (int col = 0; col < tableWidget->columnCount(); ++col) {
|
||||
// QTableWidgetItem *item = tableWidget->item(row, col);
|
||||
|
||||