Daily commit
This commit is contained in:
parent
0cebf8f1ff
commit
01aa8bca5e
@ -101,7 +101,7 @@ QString AdbUtils::getScreenDumpAsXml(const QString &deviceId, const int idx) {
|
|||||||
|
|
||||||
"adb -s %1 shell 'cat /sdcard/uidump.xml' && "
|
"adb -s %1 shell 'cat /sdcard/uidump.xml' && "
|
||||||
"adb -s %1 shell 'rm /sdcard/uidump.xml'"
|
"adb -s %1 shell 'rm /sdcard/uidump.xml'"
|
||||||
// ).arg(deviceId, QString::number(idx));
|
// ).arg(deviceId, QString::number(idx));
|
||||||
).arg(deviceId);
|
).arg(deviceId);
|
||||||
process.start("sh", {"-c", xml});
|
process.start("sh", {"-c", xml});
|
||||||
process.waitForFinished();
|
process.waitForFinished();
|
||||||
@ -221,3 +221,27 @@ bool AdbUtils::goBack(const QString &deviceId) {
|
|||||||
process.waitForFinished();
|
process.waitForFinished();
|
||||||
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QSet<QString> AdbUtils::getListApps(const QString &deviceId) {
|
||||||
|
QProcess process;
|
||||||
|
process.start("adb", {
|
||||||
|
"-s", deviceId,
|
||||||
|
"shell", "pm list",
|
||||||
|
"packages", "-3"
|
||||||
|
});
|
||||||
|
process.waitForFinished();
|
||||||
|
|
||||||
|
const QString output = process.readAllStandardOutput();
|
||||||
|
QSet<QString> apps;
|
||||||
|
|
||||||
|
const QStringList lines = output.split('\n');
|
||||||
|
for (int i = 1; i < lines.size(); ++i) {
|
||||||
|
QString line = lines.at(i).trimmed().replace("package:", "");
|
||||||
|
|
||||||
|
if (line.isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
apps.insert(line);
|
||||||
|
}
|
||||||
|
return apps;
|
||||||
|
}
|
||||||
|
|||||||
@ -34,4 +34,6 @@ public:
|
|||||||
static bool inputText(const QString &deviceId, const QString &text);
|
static bool inputText(const QString &deviceId, const QString &text);
|
||||||
|
|
||||||
static bool goBack(const QString &deviceId);
|
static bool goBack(const QString &deviceId);
|
||||||
|
|
||||||
|
static QSet<QString> getListApps(const QString &deviceId);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -262,7 +262,7 @@ void PayByPhoneScript::doStart() {
|
|||||||
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId);
|
||||||
const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appName);
|
const ApplicationInfo app = ApplicationDAO::getApplication(m_account.deviceId, m_account.appName);
|
||||||
|
|
||||||
if (device.id.isEmpty() || app.id.isEmpty()) {
|
if (device.id.isEmpty() || app.id == -1) {
|
||||||
m_error = "Device or app not found....";
|
m_error = "Device or app not found....";
|
||||||
qDebug() << m_error;
|
qDebug() << m_error;
|
||||||
emit finishedWithResult(m_error);
|
emit finishedWithResult(m_error);
|
||||||
|
|||||||
@ -1,90 +0,0 @@
|
|||||||
#include "AdbUtils.h"
|
|
||||||
#include <QProcess>
|
|
||||||
#include <QString>
|
|
||||||
#include <QDir>
|
|
||||||
#include <fstream>
|
|
||||||
|
|
||||||
AdbUtils::AdbUtils(QObject *parent) : QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AdbUtils::checkDevices() {
|
|
||||||
QProcess process;
|
|
||||||
process.start("adb", QStringList() << "devices");
|
|
||||||
process.waitForFinished();
|
|
||||||
const QString output = process.readAllStandardOutput();
|
|
||||||
qDebug() << "ADB devices output:" << output;
|
|
||||||
|
|
||||||
// Проверка на наличие строки "\tdevice" в выводе
|
|
||||||
return output.contains("\tdevice");
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AdbUtils::startApp(const QString &packageName) {
|
|
||||||
QProcess process;
|
|
||||||
process.start("adb", QStringList() << "shell" << "monkey" << "-p" << packageName << "-c" << "android.intent.category.LAUNCHER" << "1");
|
|
||||||
process.waitForFinished();
|
|
||||||
const QString output = process.readAllStandardOutput();
|
|
||||||
qDebug() << "Start app output:" << output;
|
|
||||||
|
|
||||||
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AdbUtils::dumpAndPullUiXml(const QString &localPath) {
|
|
||||||
QProcess process;
|
|
||||||
process.start("adb", QStringList() << "shell" << "uiautomator" << "dump" << "/sdcard/ui_dump.xml");
|
|
||||||
process.waitForFinished();
|
|
||||||
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
|
||||||
qDebug() << "Ошибка в получении дампа UI на устройстве";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
process.start("adb", QStringList() << "pull" << "/sdcard/ui_dump.xml" << localPath);
|
|
||||||
process.waitForFinished();
|
|
||||||
if (process.exitStatus() != QProcess::NormalExit || process.exitCode() != 0) {
|
|
||||||
qDebug() << "Ошибка в передаче xml файла на компьютер";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AdbUtils::tap(int x, int y) {
|
|
||||||
QProcess process;
|
|
||||||
process.start("adb", QStringList() << "shell" << "input" << "tap" << QString::number(x) << QString::number(y));
|
|
||||||
process.waitForFinished();
|
|
||||||
const QString output = process.readAllStandardOutput();
|
|
||||||
qDebug() << "Tap output:" << output;
|
|
||||||
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AdbUtils::swipe(const int x1, const int y1, const int x2, const int y2) {
|
|
||||||
QProcess process;
|
|
||||||
process.start("adb", QStringList() << "shell" << "input" << "swipe" << QString::number(x1) << QString::number(y1) << QString::number(x2) << QString::number(y2));
|
|
||||||
process.waitForFinished();
|
|
||||||
const QString output = process.readAllStandardOutput();
|
|
||||||
qDebug() << "Swipe output:" << output;
|
|
||||||
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AdbUtils::inputText(const QString &text) {
|
|
||||||
QProcess process;
|
|
||||||
process.start("adb", QStringList() << "shell" << "input" << "text" << text);
|
|
||||||
process.waitForFinished();
|
|
||||||
const QString output = process.readAllStandardOutput();
|
|
||||||
qDebug() << "Input text output:" << output;
|
|
||||||
return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// bool AdbUtils::takeScreenshot(const QString& filename) {
|
|
||||||
// QProcess process;
|
|
||||||
// QDir().mkdir("screens"); // Создаем папку, если её нет
|
|
||||||
// process.start("adb", QStringList() << "exec-out" << "screencap" << "-p" << ">" << "screens/" + filename);
|
|
||||||
// process.waitForFinished();
|
|
||||||
// return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// void AdbUtils::log(const std::string& message) {
|
|
||||||
// std::ofstream out("log.txt", std::ios::app);
|
|
||||||
// std::time_t t = std::time(nullptr);
|
|
||||||
// out << "[" << std::put_time(std::localtime(&t), "%H:%M:%S") << "] " << message << std::endl;
|
|
||||||
// std::cout << message << std::endl;
|
|
||||||
// }
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
#ifndef ADBUTILS_H
|
|
||||||
#define ADBUTILS_H
|
|
||||||
|
|
||||||
#include <QProcess>
|
|
||||||
|
|
||||||
class AdbUtils : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit AdbUtils(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
static bool checkDevices();
|
|
||||||
|
|
||||||
static bool startApp(const QString &packageName);
|
|
||||||
|
|
||||||
static bool tap(int x, int y);
|
|
||||||
|
|
||||||
static bool inputText(const QString &text);
|
|
||||||
|
|
||||||
static bool swipe(int x1, int y1, int x2, int y2);
|
|
||||||
|
|
||||||
static bool dumpAndPullUiXml(const QString &localPath);
|
|
||||||
|
|
||||||
// static bool takeScreenshot(const QString& filename);
|
|
||||||
|
|
||||||
// void log(const QString& message);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // ADBUTILS_H
|
|
||||||
@ -1,504 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by AmirS on 24.04.2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "Script.h"
|
|
||||||
#include "AdbUtils.h"
|
|
||||||
#include "XmlParser.h"
|
|
||||||
#include "QDebug"
|
|
||||||
#include <regex>
|
|
||||||
|
|
||||||
Script::Script(QObject *parent) : QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
Script::~Script() = default;
|
|
||||||
|
|
||||||
void Script::start() {
|
|
||||||
qDebug() << "Worker started in thread:" << QThread::currentThread();
|
|
||||||
|
|
||||||
processUiLoop();
|
|
||||||
}
|
|
||||||
|
|
||||||
[[noreturn]] void Script::processUiLoop() {
|
|
||||||
qDebug() << QString::fromUtf8("Запуск скрипта для ru.rshb.dbo");
|
|
||||||
|
|
||||||
if (!AdbUtils::checkDevices()) {
|
|
||||||
qDebug() << QString::fromUtf8("ADB: Нет устройств");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!AdbUtils::startApp("ru.rshb.dbo")) {
|
|
||||||
qDebug("Не удалось запустить приложение");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QThread::msleep(4500);
|
|
||||||
while (true) {
|
|
||||||
QString uiDumpPath = "ui_dump.xml";
|
|
||||||
AdbUtils::dumpAndPullUiXml(uiDumpPath);
|
|
||||||
|
|
||||||
QList<UiElement> elements = XmlParser::parseUiDumpUsingQXml(uiDumpPath);
|
|
||||||
|
|
||||||
switch (currentState) {
|
|
||||||
case State::HandlingSecurityBanner:
|
|
||||||
handleSecurityBanner(elements);
|
|
||||||
// Переход в состояние ожидания ПИН-кода после закрытия баннера
|
|
||||||
currentState = State::WaitingForPinInput;
|
|
||||||
|
|
||||||
case State::WaitingForPinInput:
|
|
||||||
if (XmlParser::isPinCodeScreen(elements)) {
|
|
||||||
qDebug() << "Экран ввода ПИН-кода обнаружен";
|
|
||||||
inputPinCode("1211");
|
|
||||||
currentState = State::ClosingBanner;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case State::ClosingBanner:
|
|
||||||
handleClosingBanner(elements);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case State::OnMainScreen:
|
|
||||||
if (XmlParser::isMainScreen(elements)) {
|
|
||||||
qDebug() << "State: OnMainScreen -> Поиск кнопки 'Все продукты'";
|
|
||||||
swipeToFindElement("Все продукты");
|
|
||||||
currentState = State::SearchingCard;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
|
|
||||||
case State::SearchingCard:
|
|
||||||
qDebug() << "State: SearchingCard -> Поиск блока карты";
|
|
||||||
// Добавить сюда поиск карты по последним 4 цифрам, известным заранее, для выбора карты
|
|
||||||
// currentState = State::OnCardDetails;
|
|
||||||
currentState = State::Idle;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case State::OnCardDetails:
|
|
||||||
// Обработка экрана деталей банковской карты
|
|
||||||
handleCardDetailsScreen(elements);
|
|
||||||
break;
|
|
||||||
|
|
||||||
case State::OnTransferScreen:
|
|
||||||
qDebug() << "State: OnTransferScreen -> Перевод выполнен успешно.";
|
|
||||||
// handleTransferScreen(elements);
|
|
||||||
currentState = State::Idle;
|
|
||||||
break;
|
|
||||||
|
|
||||||
case State::Idle:
|
|
||||||
default:
|
|
||||||
qDebug() << "State: Idle -> Жду следующего действия";
|
|
||||||
if (XmlParser::isSecurityBanner(elements)) {
|
|
||||||
currentState = State::HandlingSecurityBanner;
|
|
||||||
} else if (XmlParser::isPinCodeScreen(elements)) {
|
|
||||||
currentState = State::WaitingForPinInput;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
QThread::msleep(1000); // Пауза между проверками экрана
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// Функция для запуска скрипта с передачей параметров
|
|
||||||
// void Script::runScript(const QString& pin, const QString& last4digits) {
|
|
||||||
// qDebug("Запуск скрипта для ru.rshb.dbo");
|
|
||||||
//
|
|
||||||
// if (!AdbUtils::checkDevices()) {
|
|
||||||
// qDebug("ADB: Нет устройств");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!AdbUtils::startApp("ru.rshb.dbo")) {
|
|
||||||
// qDebug("Не удалось запустить приложение");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// wait(4500);
|
|
||||||
// AdbUtils::dumpAndPullUiXml("ui_01.xml");
|
|
||||||
//
|
|
||||||
// auto ui = XmlParser::parseUiDumpUsingQXml("ui_01.xml");
|
|
||||||
//
|
|
||||||
// if (XmlParser::isPinCodeScreen(ui)) {
|
|
||||||
// qDebug("Экран PIN-кода");
|
|
||||||
//
|
|
||||||
// // Находим координаты цифр на экране и вводим ПИН
|
|
||||||
// std::vector<UiElement> pinButtons = getPinButtons(ui);
|
|
||||||
// for (size_t i = 0; i < pin.length(); ++i) {
|
|
||||||
// char digit = pin[i];
|
|
||||||
// for (const auto& el : pinButtons) {
|
|
||||||
// if (el.text == std::string(1, digit)) {
|
|
||||||
// AdbUtils::tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// wait(1000);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Появляющиеся после входа по ПИН баннеры — цикл для обработки разных состояний показов модальных окон
|
|
||||||
// bool postLoginScreenFound = false;
|
|
||||||
// int attempts = 5; // Максимальное количество попыток парсинга xml
|
|
||||||
// while (attempts-- > 0) {
|
|
||||||
// AdbUtils::dumpAndPullUiXml("ui_02.xml");
|
|
||||||
// auto ui2 = XmlParser::parseUiDumpUsingQXml("ui_02.xml");
|
|
||||||
//
|
|
||||||
// // Закрытие баннеров или диалогов
|
|
||||||
// if (handlePostLoginScreen(ui2)) {
|
|
||||||
// postLoginScreenFound = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Проверка, если мы на главном экране
|
|
||||||
// if (isMainScreen(ui2)) {
|
|
||||||
// qDebug("Главный экран подтверждён");
|
|
||||||
// postLoginScreenFound = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Ждем немного перед следующим запросом дампа экрана
|
|
||||||
// wait(1000);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!postLoginScreenFound) {
|
|
||||||
// qDebug("Не удалось определить пост-логин баннер.");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Пролистывание до кнопки "Все продукты"
|
|
||||||
// AdbUtils::swipe(1000, 2000, 1000, 1300);
|
|
||||||
// wait(500);
|
|
||||||
//
|
|
||||||
// // Нажимаем на кнопку "Все продукты"
|
|
||||||
// AdbUtils::dumpAndPullUiXml("ui_03.xml");
|
|
||||||
// auto ui3 = XmlParser::parseUiDumpUsingQXml("ui_03.xml");
|
|
||||||
//
|
|
||||||
// // Ищем кнопку "Все продукты" и нажимаем на неё
|
|
||||||
// tapByText(ui3, "Все продукты");
|
|
||||||
// wait(800);
|
|
||||||
//
|
|
||||||
// // Шаг 6: Выбор карты на экране "Все продукты"
|
|
||||||
// // Запрашиваем дамп UI для поиска карт
|
|
||||||
// AdbUtils::dumpAndPullUiXml("ui_04.xml");
|
|
||||||
// auto ui4 = XmlParser::parseUiDumpUsingQXml("ui_04.xml");
|
|
||||||
//
|
|
||||||
// // Определим, какую карту выбрать, например, с 4 цифрами "0823"
|
|
||||||
// UiElement cardButton = findCardByLast4Digits(ui4, last4digits);
|
|
||||||
// if (cardButton.text.isEmpty()) {
|
|
||||||
// qDebug("Не найдена карта с номером, содержащим " + last4digits);
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Нажимаем на карту
|
|
||||||
// AdbUtils::tap((cardButton.x1 + cardButton.x2) / 2, (cardButton.y1 + cardButton.y2) / 2);
|
|
||||||
// wait(1000);
|
|
||||||
//
|
|
||||||
// // Нажимаем на кнопку "Оплатить"
|
|
||||||
// // Запрашиваем дамп UI для поиска кнопки "Оплатить"
|
|
||||||
// AdbUtils::dumpAndPullUiXml("ui_05.xml");
|
|
||||||
// auto ui5 = XmlParser::parseUiDumpUsingQXml("ui_05.xml");
|
|
||||||
//
|
|
||||||
// // Ищем кнопку "Оплатить" и нажимаем на неё
|
|
||||||
// UiElement payBtn = findPayButton(ui5);
|
|
||||||
// if (payBtn.text.isEmpty()) {
|
|
||||||
// qDebug("Не найдена кнопка 'Оплатить'");
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Нажимаем на кнопку "Оплатить"
|
|
||||||
// AdbUtils::tap((payBtn.x1 + payBtn.x2) / 2, (payBtn.y1 + payBtn.y2) / 2);
|
|
||||||
// qDebug("Нажата кнопка 'Оплатить'");
|
|
||||||
// wait(1000);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Вспомогательные функции
|
|
||||||
|
|
||||||
// Функция для нажатия по элементу, передавая элемент
|
|
||||||
void tapElement(const UiElement &el) {
|
|
||||||
const int x = (el.x1() + el.x2()) / 2;
|
|
||||||
const int y = (el.y1() + el.y2()) / 2;
|
|
||||||
// Тапаем по кнопке
|
|
||||||
AdbUtils::tap(x, y);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Функции для определения экранов и действий
|
|
||||||
|
|
||||||
void Script::handleSecurityBanner(const QList<UiElement>& elements) {
|
|
||||||
if (XmlParser::isSecurityBanner(elements)) {
|
|
||||||
qDebug() << "State: HandlingSecurityBanner -> Найден баннер о безопасности.";
|
|
||||||
|
|
||||||
// Ищем кликабельную кнопку "Отложить"
|
|
||||||
for (const UiElement& el : elements) {
|
|
||||||
if (el.text() == "Отложить" && el.isClickable() && el.className() == "android.widget.Button") {
|
|
||||||
qDebug() << "Найдена кнопка 'Отложить'. Нажимаем её!";
|
|
||||||
tapElement(el); // Нажимаем на кнопку "Отложить"
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Script::inputPinCode(const QString &pinCode) {
|
|
||||||
// Перебираем каждый символ из ПИН-кода
|
|
||||||
for (int i = 0; i < pinCode.length(); ++i) {
|
|
||||||
QString digit = pinCode.mid(i, 1);
|
|
||||||
qDebug() << "Найдена кнопка для символа: " << digit;
|
|
||||||
|
|
||||||
// Поиск кнопки с соответствующим текстом
|
|
||||||
QList<UiElement> elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml");
|
|
||||||
|
|
||||||
// Ищем кнопку с цифрой
|
|
||||||
bool found = false;
|
|
||||||
for (const UiElement &el: elements) {
|
|
||||||
if (el.text() == digit && el.isClickable()) {
|
|
||||||
tapElement(el);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если не нашли кнопку для цифры, выводим предупреждение
|
|
||||||
if (!found) {
|
|
||||||
qWarning() << "Цифровая кнопка для " << digit << " не найдена!";
|
|
||||||
}
|
|
||||||
|
|
||||||
// Пауза между нажатиями, чтобы избежать слишком быстрого ввода
|
|
||||||
QThread::msleep(200);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Script::handleClosingBanner(const QList<UiElement> &elements) {
|
|
||||||
while (true) {
|
|
||||||
// Проверка на наличие баннера или диалога
|
|
||||||
if (XmlParser::isBannerOrDialog(elements)) {
|
|
||||||
qDebug() << "State: ClosingBanner -> Найден баннер/диалог. Закрываем его.";
|
|
||||||
|
|
||||||
// Флаг для отслеживания, если мы нашли хотя бы одно окно для закрытия
|
|
||||||
bool closed = false;
|
|
||||||
|
|
||||||
for (const UiElement &el: elements) {
|
|
||||||
if (el.isClickable()) {
|
|
||||||
// Пробуем закрыть баннер, если есть кнопка "Закрыть"
|
|
||||||
if ((el.text() == "Закрыть" || el.text() == "Позже") && el.className() == "android.widget.Button") {
|
|
||||||
tapElement(el);
|
|
||||||
closed = true;
|
|
||||||
qDebug() << "Баннер/диалог закрыт!";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
// Если кнопка без текста и в правой верхней части (проверяем по координатам приблизительно)
|
|
||||||
if (el.text().isEmpty() && el.className() == "android.widget.Button" &&
|
|
||||||
el.x1() > 800 && el.y1() < 200) {
|
|
||||||
tapElement(el);
|
|
||||||
closed = true;
|
|
||||||
qDebug() << "Баннер/диалог закрыт по нажатию кнопки в правом верхнем углу.";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!closed) {
|
|
||||||
qWarning() << "Невозможно закрыть баннер или диалог!";
|
|
||||||
break; // Прерываем цикл, если не удалось закрыть окно
|
|
||||||
}
|
|
||||||
|
|
||||||
// Пауза перед следующим закрытием окна
|
|
||||||
QThread::msleep(1000);
|
|
||||||
} else {
|
|
||||||
qDebug() << "State: ClosingBanner -> Не осталось баннеров/диалогов для закрытия.";
|
|
||||||
// Заканчиваем цикл, если баннеров/диалогов больше нет
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Переход к следующему состоянию
|
|
||||||
// Если все модальные окна закрыты, переходим к основному экрану
|
|
||||||
currentState = State::OnMainScreen;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Script::swipeToFindElement(const QString &searchText) {
|
|
||||||
bool found = false;
|
|
||||||
int attempts = 0;
|
|
||||||
|
|
||||||
// Прокручиваем экран несколько раз, пока не найдем элемент или не превысим попытки
|
|
||||||
while (attempts < 5 && !found) {
|
|
||||||
QList<UiElement> elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml");
|
|
||||||
|
|
||||||
// Ищем нужный элемент на экране и если он найден - нажимаем на него
|
|
||||||
for (const UiElement &el: elements) {
|
|
||||||
if (el.text() == searchText && el.isClickable() && el.className() == "android.widget.Button") {
|
|
||||||
qDebug() << "Найден элемент: " << searchText;
|
|
||||||
tapElement(el);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если элемент не найден, выполняем свайп, прибавляем попытку и ищем снова
|
|
||||||
if (!found) {
|
|
||||||
qDebug() << "Элемент не найден, свайпаем...";
|
|
||||||
// Свайп вверх
|
|
||||||
AdbUtils::swipe(500, 1500, 500, 500);
|
|
||||||
attempts++;
|
|
||||||
// Пауза между свайпами
|
|
||||||
QThread::msleep(1000);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
qWarning() << "Элемент " << searchText << " не найден после " << attempts << " попыток свайпа.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Script::handleCardDetailsScreen(const QList<UiElement>& elements) {
|
|
||||||
// Проверяем, что мы на экране детали карты
|
|
||||||
if (XmlParser::isDetailCardScreen(elements)) {
|
|
||||||
qDebug() << "State: OnCardDetails -> Найден экран детализированной карты.";
|
|
||||||
|
|
||||||
// Ищем кликабельную кнопку "Оплатить"
|
|
||||||
bool foundPaymentButton = false;
|
|
||||||
for (const UiElement& el : elements) {
|
|
||||||
if (el.text() == "Оплатить" && el.isClickable()) {
|
|
||||||
qDebug() << "Найдена кнопка 'Оплатить'. Нажимаем её!";
|
|
||||||
// Нажимаем на кнопку "Оплатить"
|
|
||||||
tapElement(el);
|
|
||||||
foundPaymentButton = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundPaymentButton) {
|
|
||||||
qWarning() << "Payment button 'Оплатить' not found on the card details screen.";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// void Script::handleTransferScreen(const QList<UiElement> &elements) {
|
|
||||||
// // Проверка на экран перевода
|
|
||||||
// if (XmlParser::isTransferScreen(elements)) {
|
|
||||||
// qDebug() << "State: OnTransferScreen -> Looking for 'Enter amount' field or 'Confirm' button.";
|
|
||||||
//
|
|
||||||
// bool found = false;
|
|
||||||
//
|
|
||||||
// // Если поле для ввода суммы найдено, вводим сумму
|
|
||||||
// for (const UiElement &el: elements) {
|
|
||||||
// if (el.text() == "Сумма" && el.isClickable()) {
|
|
||||||
// qDebug() << "Found 'Amount' input field";
|
|
||||||
// inputAmount("1000"); // Пример суммы
|
|
||||||
// found = true;
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!found) {
|
|
||||||
// qDebug() << "Поле ввода 'Сумма' не найдено!";
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // После ввода суммы ищем кнопку 'Оплатить' или 'Подтвердить'
|
|
||||||
// for (const UiElement &el: elements) {
|
|
||||||
// if ((el.text() == "Оплатить" || el.text() == "Подтвердить") && el.isClickable()) {
|
|
||||||
// tapElement(el);
|
|
||||||
// qDebug() << "Payment confirmed.";
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
void Script::inputAmount(const QString &amount) {
|
|
||||||
// Разделяем сумму на отдельные цифры
|
|
||||||
for (int i = 0; i < amount.length(); ++i) {
|
|
||||||
QString digit = amount.mid(i, 1);
|
|
||||||
qDebug() << "Вводим цифру: " << digit;
|
|
||||||
|
|
||||||
// Поиск кнопки с соответствующей цифрой
|
|
||||||
QList<UiElement> elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml");
|
|
||||||
|
|
||||||
bool found = false;
|
|
||||||
for (const UiElement &el: elements) {
|
|
||||||
if (el.text() == digit && el.isClickable()) {
|
|
||||||
tapElement(el);
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!found) {
|
|
||||||
qWarning() << "Цифровая кнопка для " << digit << " не найдена!";
|
|
||||||
}
|
|
||||||
|
|
||||||
QThread::msleep(1000); // Пауза между нажатиями
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если нужно, нажимаем кнопку для завершения ввода суммы
|
|
||||||
QList<UiElement> elements = XmlParser::parseUiDumpUsingQXml("ui_dump.xml");
|
|
||||||
for (const UiElement &el: elements) {
|
|
||||||
if (el.isClickable()) {
|
|
||||||
if (el.text() == "ОК" || el.text() == "Подтвердить" || el.text() == "Готово") {
|
|
||||||
tapElement(el);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// void tapByText(const std::vector<UiElement> &ui, const std::string &text) {
|
|
||||||
// for (const auto &el: ui) {
|
|
||||||
// if (el.text == QString::fromStdString(text)) {
|
|
||||||
// tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Тап по полю ввода
|
|
||||||
// void tapInputField(const std::vector<UiElement> &ui, const std::string &fieldText) {
|
|
||||||
// for (const auto &el: ui) {
|
|
||||||
// if (el.text == QString::fromStdString(fieldText)) {
|
|
||||||
// tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Тап по кнопке "Далее"
|
|
||||||
// void tapNextButton(const std::vector<UiElement> &ui) {
|
|
||||||
// for (const auto &el: ui) {
|
|
||||||
// if (el.text == "Войти" || el.text == "Далее" || el.text == "Продолжить") {
|
|
||||||
// tap((el.x1 + el.x2) / 2, (el.y1 + el.y2) / 2);
|
|
||||||
// break;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// UiElement findCardByLast4Digits(const std::vector<UiElement> &ui, const std::string &last4digits) {
|
|
||||||
// // Проходим по всем элементам UI, которые парсились из XML
|
|
||||||
// for (const auto &el: ui) {
|
|
||||||
// // Используем регулярное выражение для поиска 4 цифр в тексте
|
|
||||||
// std::smatch m;
|
|
||||||
// std::string str = el.text.toUtf8().constData(); // Преобразуем QString в std::string
|
|
||||||
//
|
|
||||||
// // Если в тексте найдено совпадение с регулярным выражением для 4 цифр
|
|
||||||
// if (std::regex_search(str, m, std::regex(R"(\*\*\s?(\d{4}))"))) {
|
|
||||||
// // Проверяем, совпадают ли последние 4 цифры с переданными
|
|
||||||
// if (m[1] == last4digits) {
|
|
||||||
// // Если нашли совпадение, возвращаем элемент
|
|
||||||
// return el;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Если карта с такими последними цифрами не найдена, возвращаем пустой элемент
|
|
||||||
// return {};
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// UiElement findPayButton(const std::vector<UiElement> &ui) {
|
|
||||||
// for (const auto &el: ui) {
|
|
||||||
// if (el.text == "Оплатить") {
|
|
||||||
// return el;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// return {};
|
|
||||||
// }
|
|
||||||
|
|
||||||
void Script::stop() {
|
|
||||||
m_running = false;
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by Misterio on 24.04.2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef SCRIPT_H
|
|
||||||
#define SCRIPT_H
|
|
||||||
#include <vector>
|
|
||||||
#include <string>
|
|
||||||
#include "AdbUtils.h"
|
|
||||||
#include "XmlParser.h"
|
|
||||||
#include "auto_payment/UiElement.h"
|
|
||||||
#include <QObject>
|
|
||||||
#include <QThread>
|
|
||||||
|
|
||||||
class Script final : public QObject {
|
|
||||||
Q_OBJECT
|
|
||||||
|
|
||||||
public:
|
|
||||||
explicit Script(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
~Script() override;
|
|
||||||
|
|
||||||
public slots:
|
|
||||||
void start(); // Старт фоновой работы
|
|
||||||
|
|
||||||
void stop(); // Стоп фоновой работы
|
|
||||||
|
|
||||||
signals:
|
|
||||||
void finished(); // Сигнал о завершении работы
|
|
||||||
|
|
||||||
private:
|
|
||||||
bool m_running = true;
|
|
||||||
|
|
||||||
// // Функция для запуска скрипта с передачей параметров
|
|
||||||
// void runScript(const QString &pin, const QString &last4digits);
|
|
||||||
//
|
|
||||||
// // Функции для определения различных экранов и действий
|
|
||||||
//
|
|
||||||
// // Проверка, что перед нами экран логина
|
|
||||||
// bool isLoginScreen(const QList<UiElement> &ui);
|
|
||||||
//
|
|
||||||
// // Проверка, что перед нами экран ввода ПИН
|
|
||||||
// bool isPinScreen(const QList<UiElement> &ui);
|
|
||||||
//
|
|
||||||
// // Получение списка кнопок с цифрами для ввода ПИН
|
|
||||||
// std::vector<UiElement> getPinButtons(const QList<UiElement> &ui);
|
|
||||||
//
|
|
||||||
// // Тап по элементу с текстом
|
|
||||||
// void tapByText(const QList<UiElement> &ui, const QString &text);
|
|
||||||
//
|
|
||||||
// // Тап по полю ввода
|
|
||||||
// void tapInputField(const QList<UiElement> &ui, const QString &fieldText);
|
|
||||||
//
|
|
||||||
// // Тап по кнопке "Далее" и её вариациям
|
|
||||||
// void tapNextButton(const QList<UiElement> &ui);
|
|
||||||
//
|
|
||||||
// // Обработка экрана с пост-логин баннерами или диалогами
|
|
||||||
// bool handlePostLoginScreen(const QList<UiElement> &ui);
|
|
||||||
//
|
|
||||||
// // Проверка, что перед нами главный экран
|
|
||||||
// bool isMainScreen(const QList<UiElement> &ui);
|
|
||||||
//
|
|
||||||
// // Тап по главному экрану
|
|
||||||
// void tapMainScreen(const QList<UiElement> &ui);
|
|
||||||
//
|
|
||||||
// // Функция для поиска карты по последним 4 цифрам
|
|
||||||
// UiElement findCardByLast4Digits(const QList<UiElement> &ui, const QString &last4digits);
|
|
||||||
//
|
|
||||||
// // Поиск кнопки "Оплатить" на экране
|
|
||||||
// UiElement findPayButton(const QList<UiElement> &ui);
|
|
||||||
|
|
||||||
private:
|
|
||||||
enum class State {
|
|
||||||
HandlingSecurityBanner,
|
|
||||||
WaitingForPinInput,
|
|
||||||
ClosingBanner,
|
|
||||||
OnMainScreen,
|
|
||||||
SearchingCard,
|
|
||||||
OnCardDetails,
|
|
||||||
OnTransferScreen,
|
|
||||||
Idle
|
|
||||||
};
|
|
||||||
|
|
||||||
// Основной цикл скрипта
|
|
||||||
void processUiLoop();
|
|
||||||
|
|
||||||
// void handleState(const QList<UiElement> &elements);
|
|
||||||
|
|
||||||
static void handleSecurityBanner(const QList<UiElement>& elements);
|
|
||||||
|
|
||||||
static void inputPinCode(const QString &pinCode);
|
|
||||||
|
|
||||||
void handleClosingBanner(const QList<UiElement> &elements);
|
|
||||||
|
|
||||||
static void swipeToFindElement(const QString &searchText);
|
|
||||||
|
|
||||||
static void handleCardDetailsScreen(const QList<UiElement> &elements);
|
|
||||||
|
|
||||||
// void handleTransferScreen(const QList<UiElement> &elements);
|
|
||||||
|
|
||||||
static void inputAmount(const QString &amount);
|
|
||||||
|
|
||||||
// Утилиты для работы с ADB
|
|
||||||
AdbUtils adbUtils;
|
|
||||||
// Парсер XML UI дампов мобильного устройства
|
|
||||||
XmlParser xmlParser;
|
|
||||||
// Текущее состояние скрипта автоматизации
|
|
||||||
State currentState = State::Idle;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif //SCRIPT_H
|
|
||||||
@ -1,152 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by AmirS on 25.04.2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "XmlParser.h"
|
|
||||||
#include <QFile>
|
|
||||||
#include <QXmlStreamReader>
|
|
||||||
#include <QDebug>
|
|
||||||
|
|
||||||
XmlParser::XmlParser(QObject *parent) : QObject(parent) {
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<UiElement> XmlParser::parseUiDumpUsingQXml(const QString &filepath) {
|
|
||||||
QFile file(filepath);
|
|
||||||
QList<UiElement> elements;
|
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
|
||||||
qDebug() << "Не удалось открыть XML файл:" << filepath;
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
QXmlStreamReader xmlReader(&file);
|
|
||||||
|
|
||||||
while (!xmlReader.atEnd()) {
|
|
||||||
xmlReader.readNext();
|
|
||||||
|
|
||||||
if (xmlReader.hasError()) {
|
|
||||||
qDebug() << "Ошибка парсинга XML в линии" << xmlReader.lineNumber() << ":"
|
|
||||||
<< xmlReader.errorString();
|
|
||||||
qCritical() << "Ошибка парсинга XML: " << xmlReader.errorString()
|
|
||||||
<< ", далее вернем пустой элемент";
|
|
||||||
return {}; // Возвращаем пустоту в случае ошибки
|
|
||||||
}
|
|
||||||
|
|
||||||
// Проверка на начало элемента
|
|
||||||
if (xmlReader.isStartElement()) {
|
|
||||||
if (xmlReader.name() == "node") {
|
|
||||||
// Создаем элемент из текущего узла
|
|
||||||
UiElement element = UiElement::fromXmlNode(xmlReader);
|
|
||||||
elements.append(element);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xmlReader.hasError()) {
|
|
||||||
qWarning() << "Ошибка парсинга XML в конце:" << xmlReader.errorString();
|
|
||||||
qDebug() << "Ошибка парсинга XML в конце:" << xmlReader.errorString();
|
|
||||||
}
|
|
||||||
|
|
||||||
return elements;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool XmlParser::isSecurityBanner(const QList<UiElement>& elements) {
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
// Проверка на текст "Мы заботимся о вашей безопасности"
|
|
||||||
for (const UiElement& el : elements) {
|
|
||||||
if (el.text() == "Мы заботимся о вашей безопасности" && !el.text().isEmpty()) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Если не нашли текст, ищем кнопку "Отложить"
|
|
||||||
if (!found) {
|
|
||||||
for (const UiElement& el : elements) {
|
|
||||||
if (el.text() == "Отложить" && el.isClickable() && el.className() == "android.widget.Button") {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool XmlParser::isPinCodeScreen(const QList<UiElement> &elements) {
|
|
||||||
for (const UiElement &element: elements) {
|
|
||||||
// Проверка на наличие текста для ввода PIN
|
|
||||||
if (element.text().contains("Введите ПИН")) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool XmlParser::isBannerOrDialog(const QList<UiElement> &elements) {
|
|
||||||
bool found = false;
|
|
||||||
|
|
||||||
// 1. Проверка на наличие кнопок "Позже" и "Закрыть"
|
|
||||||
for (const UiElement &element: elements) {
|
|
||||||
// Проверка, что кнопка кликабельна и имеет текст "Позже" или "Закрыть"
|
|
||||||
if (element.isClickable()) {
|
|
||||||
if ((element.text() == "Позже" || element.text() == "Закрыть") &&
|
|
||||||
element.className() == "android.widget.Button") {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Если не нашли "Позже" или "Закрыть", ищем кнопку без текста в правой верхней части экрана
|
|
||||||
if (!found) {
|
|
||||||
for (const UiElement &element: elements) {
|
|
||||||
if (element.isClickable() && element.className() == "android.widget.Button" &&
|
|
||||||
element.text().isEmpty()) {
|
|
||||||
// Проверка, что кнопка в правой верхней части экрана
|
|
||||||
// ~ Координаты в правом верхнем углу
|
|
||||||
if (element.x1() > 800 && element.y1() < 200) {
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool XmlParser::isMainScreen(const QList<UiElement> &elements) {
|
|
||||||
for (const UiElement &element: elements) {
|
|
||||||
// Проверка на кликабельную кнопку "Все продукты"
|
|
||||||
if (element.text().contains("Все продукты") && element.isClickable()) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool XmlParser::isDetailCardScreen(const QList<UiElement> &elements) {
|
|
||||||
bool isDetailCardScreen = false;
|
|
||||||
|
|
||||||
// 1. Проверка на текст "Мои продукты"
|
|
||||||
for (const UiElement &el: elements) {
|
|
||||||
if (el.text().contains("Мои продукты") && !el.text().isEmpty()) {
|
|
||||||
isDetailCardScreen = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Если не нашли "Мои продукты", проверяем наличие кликабельной кнопки "Оплатить"
|
|
||||||
if (!isDetailCardScreen) {
|
|
||||||
for (const UiElement &el: elements) {
|
|
||||||
if (el.text() == "Оплатить" && el.isClickable() && el.className() == "android.widget.Button") {
|
|
||||||
// Кнопка "Оплатить" найдена и кликабельна
|
|
||||||
isDetailCardScreen = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return isDetailCardScreen;
|
|
||||||
}
|
|
||||||
@ -1,25 +0,0 @@
|
|||||||
#ifndef XMLPARSER_H
|
|
||||||
#define XMLPARSER_H
|
|
||||||
|
|
||||||
#include <QObject>
|
|
||||||
#include <QXmlStreamReader>
|
|
||||||
#include <QList>
|
|
||||||
#include "auto_payment/UiElement.h"
|
|
||||||
|
|
||||||
class XmlParser final : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
public:
|
|
||||||
explicit XmlParser(QObject *parent = nullptr);
|
|
||||||
|
|
||||||
static QList<UiElement> parseUiDumpUsingQXml(const QString &filepath);
|
|
||||||
static bool isSecurityBanner(const QList<UiElement>& elements);
|
|
||||||
static bool isPinCodeScreen(const QList<UiElement>& elements);
|
|
||||||
static bool isBannerOrDialog(const QList<UiElement>& elements);
|
|
||||||
static bool isMainScreen(const QList<UiElement>& elements);
|
|
||||||
static bool isDetailCardScreen(const QList<UiElement>& elements);
|
|
||||||
private:
|
|
||||||
static QList<UiElement> extractElementsFromXml(QXmlStreamReader &xmlReader);
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // XMLPARSER_H
|
|
||||||
11
config.ini
11
config.ini
@ -2,12 +2,21 @@
|
|||||||
varsion=0.0.1
|
varsion=0.0.1
|
||||||
|
|
||||||
[apps]
|
[apps]
|
||||||
list=rshb
|
list=rshb, alfabank, santanderbank
|
||||||
|
|
||||||
[rshb]
|
[rshb]
|
||||||
name=Росcельхозбанк
|
name=Росcельхозбанк
|
||||||
android_package_name=ru.rshb.dbo
|
android_package_name=ru.rshb.dbo
|
||||||
|
|
||||||
|
[alfabank]
|
||||||
|
name=Альфа-Банк
|
||||||
|
android_package_name=ru.alfabank.mobile.android
|
||||||
|
|
||||||
|
[santanderbank]
|
||||||
|
name=Santander
|
||||||
|
android_package_name=ar.com.santander.rio.mbanking
|
||||||
|
|
||||||
|
|
||||||
[net]
|
[net]
|
||||||
appInfoUpdate="http://localhost:9999/api/v1/app/update"
|
appInfoUpdate="http://localhost:9999/api/v1/app/update"
|
||||||
accountUpdate=http://localhost:9999/api/v1/account/update
|
accountUpdate=http://localhost:9999/api/v1/account/update
|
||||||
|
|||||||
Binary file not shown.
@ -1,6 +1,7 @@
|
|||||||
#include <QSqlQuery>
|
#include <QSqlQuery>
|
||||||
#include <QSqlError>
|
#include <QSqlError>
|
||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
#include <QSettings>
|
||||||
#include "ApplicationDAO.h"
|
#include "ApplicationDAO.h"
|
||||||
#include "DatabaseManager.h"
|
#include "DatabaseManager.h"
|
||||||
#include "db/ApplicationInfo.h"
|
#include "db/ApplicationInfo.h"
|
||||||
@ -9,27 +10,31 @@ bool ApplicationDAO::upsertApplication(const ApplicationInfo &info) {
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
INSERT INTO applications (id, device_id, pin_code, name, package, status, comment, image)
|
INSERT INTO applications (id, device_id, pin_code, name, code, package, status, comment, install, pin_code_checked)
|
||||||
VALUES (:id, :device_id, :pin_code, :name, :package, :status, :comment, :image)
|
VALUES (:id, :device_id, :pin_code, :name, :code, :package, :status, :comment, :install, :pin_code_checked)
|
||||||
ON CONFLICT(device_id, name)
|
ON CONFLICT(device_id, code)
|
||||||
DO UPDATE SET
|
DO UPDATE SET
|
||||||
device_id = excluded.device_id,
|
device_id = excluded.device_id,
|
||||||
pin_code = excluded.pin_code,
|
pin_code = excluded.pin_code,
|
||||||
name = excluded.name,
|
name = excluded.name,
|
||||||
|
code = excluded.code,
|
||||||
package = excluded.package,
|
package = excluded.package,
|
||||||
status = excluded.status,
|
status = excluded.status,
|
||||||
comment = excluded.comment,
|
comment = excluded.comment,
|
||||||
image = excluded.image
|
install = excluded.install,
|
||||||
|
pin_code_checked = excluded.pin_code_checked,
|
||||||
)");
|
)");
|
||||||
|
|
||||||
query.bindValue(":id", info.id);
|
query.bindValue(":id", info.id);
|
||||||
query.bindValue(":device_id", info.deviceId);
|
query.bindValue(":device_id", info.deviceId);
|
||||||
query.bindValue(":pin_code", info.pinCode);
|
query.bindValue(":pin_code", info.pinCode);
|
||||||
query.bindValue(":name", info.name);
|
query.bindValue(":name", info.name);
|
||||||
|
query.bindValue(":code", info.code);
|
||||||
query.bindValue(":package", info.package);
|
query.bindValue(":package", info.package);
|
||||||
query.bindValue(":status", info.status);
|
query.bindValue(":status", info.status);
|
||||||
query.bindValue(":comment", info.comment);
|
query.bindValue(":comment", info.comment);
|
||||||
query.bindValue(":image", info.image);
|
query.bindValue(":install", info.install ? "yes" : "no");
|
||||||
|
query.bindValue(":pin_code_checked", info.pinCodeChecked ? "yes" : "no");
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "Ошибка при вставке/обновлении application:" << query.lastError().text();
|
qWarning() << "Ошибка при вставке/обновлении application:" << query.lastError().text();
|
||||||
@ -55,35 +60,63 @@ bool ApplicationDAO::updatePinCode(const QString &appId, const QString &pinCode)
|
|||||||
qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text();
|
qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ApplicationDAO::update(
|
||||||
|
const int &appId,
|
||||||
|
const QString &pinCode,
|
||||||
|
const QString &comment,
|
||||||
|
const QString &status
|
||||||
|
) {
|
||||||
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
|
query.prepare(R"(
|
||||||
|
UPDATE applications
|
||||||
|
SET pin_code = :pin_code, comment = :comment, status = :status
|
||||||
|
WHERE id = :id
|
||||||
|
)");
|
||||||
|
|
||||||
|
query.bindValue(":id", appId);
|
||||||
|
query.bindValue(":pin_code", pinCode);
|
||||||
|
query.bindValue(":comment", comment);
|
||||||
|
query.bindValue(":pin_code", pinCode);
|
||||||
|
query.bindValue(":status", status);
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationInfo parseApplication(const QSqlQuery &query) {
|
ApplicationInfo parseApplication(const QSqlQuery &query) {
|
||||||
ApplicationInfo app;
|
ApplicationInfo app;
|
||||||
app.id = query.value("id").toString();
|
app.id = query.value("id").toInt();
|
||||||
app.deviceId = query.value("device_id").toString();
|
app.deviceId = query.value("device_id").toString();
|
||||||
app.pinCode = query.value("pin_code").toString();
|
app.pinCode = query.value("pin_code").toString();
|
||||||
|
app.code = query.value("code").toString();
|
||||||
app.name = query.value("name").toString();
|
app.name = query.value("name").toString();
|
||||||
app.package = query.value("package").toString();
|
app.package = query.value("package").toString();
|
||||||
app.status = query.value("status").toString();
|
app.status = query.value("status").toString();
|
||||||
app.comment = query.value("comment").toString();
|
app.comment = query.value("comment").toString();
|
||||||
app.image = query.value("image").toByteArray();
|
app.install = query.value("install").toString() == "yes";
|
||||||
|
app.pinCodeChecked = query.value("pin_code_checked").toString() == "yes";
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationInfo ApplicationDAO::getApplication(const QString &deviceId, const QString &appName) {
|
ApplicationInfo ApplicationDAO::getApplication(const QString &deviceId, const QString &appCode) {
|
||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, pin_code, name, package, status, comment, image
|
SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked
|
||||||
FROM applications
|
FROM applications
|
||||||
WHERE device_id = :device_id AND name = :name
|
WHERE device_id = :device_id AND code = :code
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
)");
|
)");
|
||||||
|
|
||||||
query.bindValue(":device_id", deviceId);
|
query.bindValue(":device_id", deviceId);
|
||||||
query.bindValue(":name", appName);
|
query.bindValue(":code", appCode);
|
||||||
|
|
||||||
if (!query.exec()) {
|
if (!query.exec()) {
|
||||||
qWarning() << "Ошибка при получении приложений по deviceId:" << query.lastError().text();
|
qWarning() << "Ошибка при получении приложений по deviceId:" << query.lastError().text();
|
||||||
@ -100,7 +133,7 @@ QList<ApplicationInfo> ApplicationDAO::getApplicationsByDeviceId(const QString &
|
|||||||
QSqlQuery query(DatabaseManager::instance().database());
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
QList<ApplicationInfo> list;
|
QList<ApplicationInfo> list;
|
||||||
query.prepare(R"(
|
query.prepare(R"(
|
||||||
SELECT id, device_id, pin_code, name, package, status, comment, image
|
SELECT id, device_id, pin_code, code, name, package, status, comment, install, pin_code_checked
|
||||||
FROM applications
|
FROM applications
|
||||||
WHERE device_id = :device_id
|
WHERE device_id = :device_id
|
||||||
)");
|
)");
|
||||||
@ -118,3 +151,38 @@ QList<ApplicationInfo> ApplicationDAO::getApplicationsByDeviceId(const QString &
|
|||||||
|
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ApplicationDAO::checkInstalledApps(const QString &deviceId, const QSet<QString> &apps) {
|
||||||
|
QSqlQuery query(DatabaseManager::instance().database());
|
||||||
|
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||||
|
QStringList banks = settings.value("apps/list", QStringList()).toStringList();
|
||||||
|
|
||||||
|
for (const QString &bank: banks) {
|
||||||
|
QString package = settings.value(bank + "/android_package_name").toString();
|
||||||
|
QString name = settings.value(bank + "/name").toString();
|
||||||
|
|
||||||
|
query.prepare(R"(
|
||||||
|
INSERT INTO applications (device_id, name, code, package, status, install, pin_code_checked)
|
||||||
|
VALUES (:device_id, :name, :code, :package, :status, :install, :pin_code_checked)
|
||||||
|
ON CONFLICT(device_id, code)
|
||||||
|
DO UPDATE SET
|
||||||
|
install = excluded.install
|
||||||
|
)");
|
||||||
|
|
||||||
|
query.bindValue(":device_id", deviceId);
|
||||||
|
query.bindValue(":code", bank);
|
||||||
|
query.bindValue(":name", name);
|
||||||
|
query.bindValue(":package", package);
|
||||||
|
query.bindValue(":status", "off");
|
||||||
|
query.bindValue(":pin_code_checked", "no");
|
||||||
|
query.bindValue(":install", apps.contains(package) ? "yes" : "no");
|
||||||
|
|
||||||
|
if (!query.exec()) {
|
||||||
|
qWarning() << "Ошибка при проверке приложений:" << query.lastError().text();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|||||||
@ -8,7 +8,12 @@ public:
|
|||||||
|
|
||||||
[[nodiscard]] static bool updatePinCode(const QString &appId, const QString &pinCode);
|
[[nodiscard]] static bool updatePinCode(const QString &appId, const QString &pinCode);
|
||||||
|
|
||||||
[[nodiscard]] static ApplicationInfo getApplication(const QString &deviceId, const QString &appName);
|
[[nodiscard]] static bool update(const int &appId, const QString &pinCode, const QString &comment,
|
||||||
|
const QString &status);
|
||||||
|
|
||||||
|
[[nodiscard]] static ApplicationInfo getApplication(const QString &deviceId, const QString &appName);
|
||||||
|
|
||||||
[[nodiscard]] static QList<ApplicationInfo> getApplicationsByDeviceId(const QString &deviceId);
|
[[nodiscard]] static QList<ApplicationInfo> getApplicationsByDeviceId(const QString &deviceId);
|
||||||
|
|
||||||
|
[[nodiscard]] static bool checkInstalledApps(const QString &deviceId, const QSet<QString> &apps);
|
||||||
};
|
};
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 826 B After Width: | Height: | Size: 826 B |
BIN
images/rshb_icon.png
Normal file
BIN
images/rshb_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
BIN
images/santanderbank_icon.png
Normal file
BIN
images/santanderbank_icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1014 B |
5
main.cpp
5
main.cpp
@ -8,7 +8,6 @@
|
|||||||
#include "database/dao/CommonDataDAO.h"
|
#include "database/dao/CommonDataDAO.h"
|
||||||
#include "db/AccountInfoScreener.h"
|
#include "db/AccountInfoScreener.h"
|
||||||
#include "services/DeviceScreener.h"
|
#include "services/DeviceScreener.h"
|
||||||
#include "automation_script/Script.h"
|
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <QStyleFactory>
|
#include <QStyleFactory>
|
||||||
@ -22,7 +21,7 @@
|
|||||||
#include "rshb/HomeScreenScript.h"
|
#include "rshb/HomeScreenScript.h"
|
||||||
#include "rshb/PayByPhoneScript.h"
|
#include "rshb/PayByPhoneScript.h"
|
||||||
|
|
||||||
void setupWorkerAndThread(QCoreApplication &app) {
|
void startDeviceScreener(QCoreApplication &app) {
|
||||||
auto *thread = new QThread;
|
auto *thread = new QThread;
|
||||||
auto *deviceScreenerWorker = new DeviceScreener;
|
auto *deviceScreenerWorker = new DeviceScreener;
|
||||||
|
|
||||||
@ -222,7 +221,7 @@ int main(int argc, char *argv[]) {
|
|||||||
// QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
|
// QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
|
||||||
// paymentThread->start();
|
// paymentThread->start();
|
||||||
//
|
//
|
||||||
// setupWorkerAndThread(app);
|
startDeviceScreener(app);
|
||||||
|
|
||||||
MainWindow window;
|
MainWindow window;
|
||||||
window.show();
|
window.show();
|
||||||
|
|||||||
@ -14,16 +14,18 @@ CREATE TABLE IF NOT EXISTS devices
|
|||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS applications
|
CREATE TABLE IF NOT EXISTS applications
|
||||||
(
|
(
|
||||||
id TEXT PRIMARY KEY,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
device_id TEXT,
|
device_id TEXT,
|
||||||
pin_code TEXT,
|
pin_code TEXT,
|
||||||
name TEXT,
|
pin_code_checked TEXT,
|
||||||
package TEXT,
|
name TEXT,
|
||||||
status TEXT,
|
code TEXT,
|
||||||
comment TEXT,
|
package TEXT,
|
||||||
image BLOB,
|
status TEXT,
|
||||||
|
install TEXT,
|
||||||
|
comment TEXT,
|
||||||
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE,
|
||||||
UNIQUE (device_id, name)
|
UNIQUE (device_id, code)
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS accounts
|
CREATE TABLE IF NOT EXISTS accounts
|
||||||
|
|||||||
@ -1,47 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by AmirS on 26.04.2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "UiElement.h"
|
|
||||||
#include <QXmlStreamReader>
|
|
||||||
#include <QRegularExpression>
|
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
UiElement::UiElement(const int x1, const int y1, const int x2, const int y2, QString text, QString className,
|
|
||||||
const bool clickable)
|
|
||||||
: m_x1(x1), m_y1(y1), m_x2(x2), m_y2(y2), m_text(std::move(text)), m_className(std::move(className)),
|
|
||||||
m_clickable(clickable) {
|
|
||||||
}
|
|
||||||
|
|
||||||
static const QRegularExpression boundsRegex(R"(\[([0-9]+),([0-9]+)\]\[([0-9]+),([0-9]+)\])");
|
|
||||||
|
|
||||||
UiElement UiElement::fromXmlNode(const QXmlStreamReader &xmlReader) {
|
|
||||||
int x1 = 0, y1 = 0, x2 = 0, y2 = 0;
|
|
||||||
QString text, className;
|
|
||||||
bool clickable = false;
|
|
||||||
|
|
||||||
if (xmlReader.attributes().hasAttribute("bounds")) {
|
|
||||||
QString bounds = xmlReader.attributes().value("bounds").toString();
|
|
||||||
QRegularExpressionMatch match = boundsRegex.match(bounds);
|
|
||||||
if (match.hasMatch()) {
|
|
||||||
x1 = match.captured(1).toInt();
|
|
||||||
y1 = match.captured(2).toInt();
|
|
||||||
x2 = match.captured(3).toInt();
|
|
||||||
y2 = match.captured(4).toInt();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xmlReader.attributes().hasAttribute("text")) {
|
|
||||||
text = xmlReader.attributes().value("text").toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xmlReader.attributes().hasAttribute("class")) {
|
|
||||||
className = xmlReader.attributes().value("class").toString();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (xmlReader.attributes().hasAttribute("clickable")) {
|
|
||||||
clickable = xmlReader.attributes().value("clickable").toString() == "true";
|
|
||||||
}
|
|
||||||
|
|
||||||
return UiElement(x1, y1, x2, y2, text, className, clickable);
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
//
|
|
||||||
// Created by AmirS on 26.04.2025.
|
|
||||||
//
|
|
||||||
|
|
||||||
#ifndef UIELEMENT_H
|
|
||||||
#define UIELEMENT_H
|
|
||||||
|
|
||||||
#include <QXmlStreamReader>
|
|
||||||
|
|
||||||
class UiElement {
|
|
||||||
public:
|
|
||||||
explicit UiElement(int x1 = 0, int y1 = 0, int x2 = 0, int y2 = 0,
|
|
||||||
QString text = "", QString className = "", bool clickable = false);
|
|
||||||
|
|
||||||
[[nodiscard]] int x1() const { return m_x1; }
|
|
||||||
[[nodiscard]] int y1() const { return m_y1; }
|
|
||||||
[[nodiscard]] int x2() const { return m_x2; }
|
|
||||||
[[nodiscard]] int y2() const { return m_y2; }
|
|
||||||
[[nodiscard]] QString text() const { return m_text; }
|
|
||||||
[[nodiscard]] QString className() const { return m_className; }
|
|
||||||
[[nodiscard]] bool isClickable() const { return m_clickable; }
|
|
||||||
|
|
||||||
static UiElement fromXmlNode(const QXmlStreamReader &xmlReader);
|
|
||||||
|
|
||||||
private:
|
|
||||||
int m_x1, m_y1, m_x2, m_y2;
|
|
||||||
QString m_text;
|
|
||||||
QString m_className;
|
|
||||||
bool m_clickable;
|
|
||||||
};
|
|
||||||
|
|
||||||
#endif // UIELEMENT_H
|
|
||||||
@ -23,7 +23,7 @@ inline AccountStatus accountStatusFromString(const QString &str) {
|
|||||||
|
|
||||||
struct AccountInfo {
|
struct AccountInfo {
|
||||||
int id = -1;
|
int id = -1;
|
||||||
QString deviceId;
|
QString deviceId; // code
|
||||||
QString appName;
|
QString appName;
|
||||||
QString lastNumbers;
|
QString lastNumbers;
|
||||||
double amount = 0.0;
|
double amount = 0.0;
|
||||||
|
|||||||
@ -5,13 +5,15 @@
|
|||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|
||||||
struct ApplicationInfo {
|
struct ApplicationInfo {
|
||||||
QString id;
|
int id = -1;
|
||||||
|
bool install = false;
|
||||||
|
bool pinCodeChecked = false;
|
||||||
QString deviceId;
|
QString deviceId;
|
||||||
QString pinCode;
|
QString pinCode;
|
||||||
|
QString code;
|
||||||
QString name;
|
QString name;
|
||||||
QString package;
|
QString package;
|
||||||
QString status;
|
QString status;
|
||||||
QString comment;
|
QString comment;
|
||||||
QByteArray image;
|
|
||||||
QDateTime updateTime;
|
QDateTime updateTime;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -15,6 +15,9 @@
|
|||||||
#include <dao/DeviceDAO.h>
|
#include <dao/DeviceDAO.h>
|
||||||
#include <db/DeviceInfo.h>
|
#include <db/DeviceInfo.h>
|
||||||
|
|
||||||
|
#include "adb/AdbUtils.h"
|
||||||
|
#include "dao/ApplicationDAO.h"
|
||||||
|
|
||||||
DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
|
DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) {
|
||||||
const QSettings settings("config.ini", QSettings::IniFormat);
|
const QSettings settings("config.ini", QSettings::IniFormat);
|
||||||
m_urlAppUpdate = settings.value("net/appInfoUpdate").toString();
|
m_urlAppUpdate = settings.value("net/appInfoUpdate").toString();
|
||||||
@ -95,6 +98,7 @@ void DeviceScreener::postDevicesData(const QList<DeviceInfo> &devices) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DeviceScreener::start() {
|
void DeviceScreener::start() {
|
||||||
|
QMap<QString, bool> bankChecked;
|
||||||
while (m_running) {
|
while (m_running) {
|
||||||
QList<QPair<QString, QString> > devices = readAdbDevices();
|
QList<QPair<QString, QString> > devices = readAdbDevices();
|
||||||
QStringList onlineIds;
|
QStringList onlineIds;
|
||||||
@ -103,14 +107,15 @@ void DeviceScreener::start() {
|
|||||||
DeviceInfo device;
|
DeviceInfo device;
|
||||||
device.id = id;
|
device.id = id;
|
||||||
|
|
||||||
|
|
||||||
if (status == "device") {
|
if (status == "device") {
|
||||||
onlineIds << device.id;
|
onlineIds << device.id;
|
||||||
device = readDeviceInfo(id);
|
const bool isChecked = bankChecked.contains(id);
|
||||||
|
device = readDeviceInfo(id, isChecked);
|
||||||
device.image = takeScreenshot(id);
|
device.image = takeScreenshot(id);
|
||||||
if (!DeviceDAO::updateImage(id, device.image)) {
|
if (!DeviceDAO::updateImage(id, device.image)) {
|
||||||
qDebug() << "Cant update screenshot";
|
qDebug() << "Cant update screenshot";
|
||||||
}
|
}
|
||||||
|
bankChecked[id] = true;
|
||||||
}
|
}
|
||||||
device.status = status;
|
device.status = status;
|
||||||
device.updateTime = QDateTime::currentDateTime();
|
device.updateTime = QDateTime::currentDateTime();
|
||||||
@ -201,7 +206,7 @@ QList<QPair<QString, QString> > DeviceScreener::readAdbDevices() {
|
|||||||
static const QRegularExpression screenRegex(R"(Physical size: (\d+)x(\d+))");
|
static const QRegularExpression screenRegex(R"(Physical size: (\d+)x(\d+))");
|
||||||
static const QRegularExpression batteryRegex(R"(level:\s*(\d+))");
|
static const QRegularExpression batteryRegex(R"(level:\s*(\d+))");
|
||||||
|
|
||||||
DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId) {
|
DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId, const bool isChecked) {
|
||||||
DeviceInfo info;
|
DeviceInfo info;
|
||||||
info.id = deviceId;
|
info.id = deviceId;
|
||||||
info.screenWidth = 0;
|
info.screenWidth = 0;
|
||||||
@ -233,6 +238,15 @@ DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId) {
|
|||||||
info.battery = match.captured(1).toInt();
|
info.battery = match.captured(1).toInt();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!isChecked) {
|
||||||
|
QSet<QString> apps = AdbUtils::getListApps(deviceId);
|
||||||
|
if (!ApplicationDAO::checkInstalledApps(deviceId, apps)) {
|
||||||
|
qWarning() << "Cant check installed apps";
|
||||||
|
} else {
|
||||||
|
qDebug() << "Apps checked: " + deviceId;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
info.updateTime = QDateTime::currentDateTime();
|
info.updateTime = QDateTime::currentDateTime();
|
||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,7 +27,7 @@ private:
|
|||||||
|
|
||||||
static QByteArray takeScreenshot(const QString &deviceId);
|
static QByteArray takeScreenshot(const QString &deviceId);
|
||||||
|
|
||||||
static DeviceInfo readDeviceInfo(const QString &deviceId);
|
static DeviceInfo readDeviceInfo(const QString &deviceId, bool isChecked);
|
||||||
|
|
||||||
void postDevicesData(const QList<DeviceInfo> &devices);
|
void postDevicesData(const QList<DeviceInfo> &devices);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -24,6 +24,180 @@ QTableWidgetItem *BankSettingsWindow::createTableWidget(const QString &text) {
|
|||||||
return item;
|
return item;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString parseStatus(const ApplicationInfo &app) {
|
||||||
|
if (!app.install) {
|
||||||
|
return "не установлен";
|
||||||
|
}
|
||||||
|
if (app.status == "active") {
|
||||||
|
if (!app.pinCodeChecked) {
|
||||||
|
return app.pinCode.isEmpty() ? "нет пин-код" : "ин-код не проверен";
|
||||||
|
}
|
||||||
|
return "работает";
|
||||||
|
} else if (app.status == "off") {
|
||||||
|
return "выключено";
|
||||||
|
} else {
|
||||||
|
return app.status;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void BankSettingsWindow::reloadContent(QTableWidget *tableWidget) {
|
||||||
|
QList<ApplicationInfo> applications = ApplicationDAO::getApplicationsByDeviceId(m_deviceId);
|
||||||
|
|
||||||
|
tableWidget->setRowCount(applications.length());
|
||||||
|
tableWidget->setColumnCount(7);
|
||||||
|
tableWidget->setHorizontalHeaderLabels({
|
||||||
|
"",
|
||||||
|
"Имя",
|
||||||
|
"Пин-код",
|
||||||
|
"Статус",
|
||||||
|
"Комментарий",
|
||||||
|
"",
|
||||||
|
""
|
||||||
|
});
|
||||||
|
|
||||||
|
tableWidget->setWordWrap(true);
|
||||||
|
tableWidget->setTextElideMode(Qt::ElideNone);
|
||||||
|
|
||||||
|
QMargins cellPadding{8, 4, 8, 4};
|
||||||
|
auto *delegate = new PaddedItemDelegate(cellPadding, tableWidget);
|
||||||
|
// tableWidget->setItemDelegateForColumn(1, delegate);
|
||||||
|
// tableWidget->setItemDelegateForColumn(2, delegate);
|
||||||
|
// tableWidget->setItemDelegateForColumn(3, delegate);
|
||||||
|
// tableWidget->setItemDelegateForColumn(4, delegate);
|
||||||
|
|
||||||
|
tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
|
for (int i = 0; i < 7; ++i) {
|
||||||
|
tableWidget->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||||
|
tableWidget->verticalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
||||||
|
}
|
||||||
|
tableWidget->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch);
|
||||||
|
|
||||||
|
|
||||||
|
for (int k = 0; k < applications.size(); ++k) {
|
||||||
|
const ApplicationInfo &app = applications[k];
|
||||||
|
|
||||||
|
|
||||||
|
auto *icon = new QLabel(tableWidget);
|
||||||
|
QPixmap pixmap(QCoreApplication::applicationDirPath() + "/images/" + app.code + "_icon.png");
|
||||||
|
icon->setPixmap(pixmap);
|
||||||
|
icon->setPixmap(pixmap.scaled(20, 20, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||||
|
icon->setStyleSheet(
|
||||||
|
"background-color: transparent;"
|
||||||
|
"text-align: center;"
|
||||||
|
"padding:0 4px 0 0;"
|
||||||
|
);
|
||||||
|
icon->setMinimumSize(24, 20);
|
||||||
|
tableWidget->setCellWidget(k, 0, icon);
|
||||||
|
|
||||||
|
tableWidget->setItem(k, 1, createTableWidget(app.name));
|
||||||
|
|
||||||
|
|
||||||
|
// QTableWidgetItem *picCodeItem = new QTableWidgetItem(app.pinCode);
|
||||||
|
// picCodeItem->setTextAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
||||||
|
auto *picCode = new QLabel(app.pinCode);
|
||||||
|
if (!app.pinCode.isEmpty() && !app.pinCodeChecked) {
|
||||||
|
picCode->setStyleSheet(
|
||||||
|
"background-color: orange;"
|
||||||
|
"color: white;"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
picCode->setAlignment(Qt::AlignCenter);
|
||||||
|
tableWidget->setCellWidget(k, 2, picCode);
|
||||||
|
|
||||||
|
tableWidget->setItem(k, 3, createTableWidget(parseStatus(app)));
|
||||||
|
|
||||||
|
// tableWidget->setItem(k, 4, createTableWidget(app.comment));
|
||||||
|
|
||||||
|
QTableWidgetItem *item = new QTableWidgetItem(app.comment);
|
||||||
|
item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop);
|
||||||
|
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
||||||
|
// item->setSizeHint(QSize(100, app.comment.length() / 2)); // Минимальная высота ячейки для переноса
|
||||||
|
tableWidget->setItem(k, 4, item);
|
||||||
|
|
||||||
|
|
||||||
|
if (app.install) {
|
||||||
|
if (!app.pinCode.isEmpty() && !app.pinCodeChecked) {
|
||||||
|
auto *pinCodeBtn = new QPushButton(" Проверить пин-код ", tableWidget);
|
||||||
|
pinCodeBtn->setProperty("btnClass", "pincode");
|
||||||
|
tableWidget->setCellWidget(k, 5, pinCodeBtn);
|
||||||
|
|
||||||
|
connect(pinCodeBtn, &QPushButton::clicked, this, [this,tableWidget, app]() {
|
||||||
|
QMessageBox msgBox(this);
|
||||||
|
msgBox.setWindowTitle(tr("Проверка пин-код"));
|
||||||
|
msgBox.setText("Сейчас запустится скрипт проверки пин-код в приложении: " + app.name);
|
||||||
|
|
||||||
|
QPushButton *deleteBtn = msgBox.addButton(tr("Запустить"), QMessageBox::AcceptRole);
|
||||||
|
QPushButton *closeBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole);
|
||||||
|
msgBox.setDefaultButton(closeBtn);
|
||||||
|
msgBox.setModal(true);
|
||||||
|
msgBox.exec();
|
||||||
|
|
||||||
|
if (msgBox.clickedButton() == deleteBtn) {
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
if (app.status == "active" && !app.pinCode.isEmpty()) {
|
||||||
|
auto *deleteBtn = new QPushButton(" Отключить ", tableWidget);
|
||||||
|
deleteBtn->setProperty("btnClass", "delete");
|
||||||
|
// deleteBtn->setStyleSheet("padding: 4px 8px;");
|
||||||
|
// deleteBtn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||||
|
tableWidget->setCellWidget(k, 5, deleteBtn);
|
||||||
|
|
||||||
|
connect(deleteBtn, &QPushButton::clicked, this, [this,tableWidget, app]() {
|
||||||
|
QMessageBox msgBox(this);
|
||||||
|
msgBox.setWindowTitle(tr("Подтверждение"));
|
||||||
|
msgBox.setText("Вы уверены, что хотите отключить от работы данный банк: " + app.name + "?");
|
||||||
|
|
||||||
|
QPushButton *deleteBtn = msgBox.addButton(tr("Отключить"), QMessageBox::AcceptRole);
|
||||||
|
QPushButton *closeBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole);
|
||||||
|
msgBox.setDefaultButton(closeBtn);
|
||||||
|
msgBox.setModal(true);
|
||||||
|
msgBox.exec();
|
||||||
|
|
||||||
|
if (msgBox.clickedButton() == deleteBtn) {
|
||||||
|
if (!ApplicationDAO::update(app.id, app.pinCode, app.comment, "off")) {
|
||||||
|
qDebug() << "Cant update bank data";
|
||||||
|
} else {
|
||||||
|
tableWidget->clearContents();
|
||||||
|
reloadContent(tableWidget);
|
||||||
|
tableWidget->resizeRowsToContents();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// просто закрыли — ничего не делаем
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// auto *editBtn = new QTableWidgetItem;
|
||||||
|
// tableWidget->setItem(k, 5, editBtn);
|
||||||
|
auto *btn = new QPushButton(" Редактировать ", tableWidget);
|
||||||
|
btn->setProperty("btnClass", "edit");
|
||||||
|
// btn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
||||||
|
tableWidget->setCellWidget(k, 6, btn);
|
||||||
|
connect(btn, &QPushButton::clicked, this, [this, tableWidget, k,app]() {
|
||||||
|
BankEditDialog dlg(app, this);
|
||||||
|
if (dlg.exec() == QDialog::Accepted) {
|
||||||
|
const QString pinCode = dlg.firstValue();
|
||||||
|
const QString comment = dlg.secondValue();
|
||||||
|
bool on = dlg.toggled();
|
||||||
|
|
||||||
|
if (!ApplicationDAO::update(app.id, pinCode, comment, on ? "active" : "off")) {
|
||||||
|
qDebug() << "Cant update bank data";
|
||||||
|
} else {
|
||||||
|
tableWidget->clearContents();
|
||||||
|
reloadContent(tableWidget);
|
||||||
|
tableWidget->resizeRowsToContents();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Пользователь нажал «Cancel» или закрыл окно
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QString deviceName)
|
BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QString deviceName)
|
||||||
: QWidget(parent,
|
: QWidget(parent,
|
||||||
Qt::Window
|
Qt::Window
|
||||||
@ -34,10 +208,10 @@ BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QStrin
|
|||||||
m_deviceId(std::move(deviceId)),
|
m_deviceId(std::move(deviceId)),
|
||||||
m_deviceName(std::move(deviceName)
|
m_deviceName(std::move(deviceName)
|
||||||
) {
|
) {
|
||||||
setWindowTitle("Настройки банка [" + m_deviceName + "]");
|
setWindowTitle("Настройки банков [" + m_deviceName + "]");
|
||||||
setMinimumSize(820, 400);
|
setMinimumSize(800, 400);
|
||||||
setMaximumSize(820, 400);
|
setMaximumSize(1000, 800);
|
||||||
resize(820, 400);
|
resize(800, 400);
|
||||||
|
|
||||||
auto *mainLayout = new QVBoxLayout(this);
|
auto *mainLayout = new QVBoxLayout(this);
|
||||||
|
|
||||||
@ -55,7 +229,6 @@ BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QStrin
|
|||||||
scrollArea->setStyleSheet("border: none;"); // Убираем рамку
|
scrollArea->setStyleSheet("border: none;"); // Убираем рамку
|
||||||
layout->setAlignment(Qt::AlignTop);
|
layout->setAlignment(Qt::AlignTop);
|
||||||
|
|
||||||
QList<ApplicationInfo> applications = ApplicationDAO::getApplicationsByDeviceId(m_deviceId);
|
|
||||||
|
|
||||||
QTableWidget *tableWidget = new QTableWidget(this);
|
QTableWidget *tableWidget = new QTableWidget(this);
|
||||||
tableWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
tableWidget->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
@ -95,6 +268,9 @@ BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QStrin
|
|||||||
"QPushButton[btnClass='delete'] {"
|
"QPushButton[btnClass='delete'] {"
|
||||||
"background-color: red;"
|
"background-color: red;"
|
||||||
"}"
|
"}"
|
||||||
|
"QPushButton[btnClass='pincode'] {"
|
||||||
|
"background-color: orange;"
|
||||||
|
"}"
|
||||||
"QPushButton[btnClass='edit'] {"
|
"QPushButton[btnClass='edit'] {"
|
||||||
"background-color: green;"
|
"background-color: green;"
|
||||||
"}"
|
"}"
|
||||||
@ -105,108 +281,11 @@ BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QStrin
|
|||||||
"background-color: blue;"
|
"background-color: blue;"
|
||||||
"}"
|
"}"
|
||||||
);
|
);
|
||||||
tableWidget->setRowCount(applications.length());
|
|
||||||
tableWidget->setColumnCount(7);
|
|
||||||
tableWidget->setHorizontalHeaderLabels({
|
|
||||||
"",
|
|
||||||
"Имя",
|
|
||||||
"Пин-код",
|
|
||||||
"Статус",
|
|
||||||
"Комментарий",
|
|
||||||
"",
|
|
||||||
""
|
|
||||||
});
|
|
||||||
|
|
||||||
tableWidget->setWordWrap(true);
|
tableWidget->clearContents();
|
||||||
tableWidget->setTextElideMode(Qt::ElideNone);
|
reloadContent(tableWidget);
|
||||||
|
tableWidget->resizeColumnsToContents();
|
||||||
|
|
||||||
QMargins cellPadding{8, 4, 8, 4};
|
|
||||||
auto *delegate = new PaddedItemDelegate(cellPadding, tableWidget);
|
|
||||||
tableWidget->setItemDelegateForColumn(1, delegate);
|
|
||||||
tableWidget->setItemDelegateForColumn(2, delegate);
|
|
||||||
tableWidget->setItemDelegateForColumn(3, delegate);
|
|
||||||
tableWidget->setItemDelegateForColumn(4, delegate);
|
|
||||||
|
|
||||||
tableWidget->horizontalHeader()->setDefaultAlignment(Qt::AlignLeft | Qt::AlignVCenter);
|
|
||||||
for (int i = 0; i < 7; ++i) {
|
|
||||||
tableWidget->horizontalHeader()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
|
|
||||||
}
|
|
||||||
tableWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
|
|
||||||
tableWidget->horizontalHeader()->setSectionResizeMode(4, QHeaderView::Stretch);
|
|
||||||
|
|
||||||
|
|
||||||
for (int k = 0; k < applications.size(); ++k) {
|
|
||||||
const ApplicationInfo &app = applications[k];
|
|
||||||
|
|
||||||
|
|
||||||
auto *icon = new QLabel(tableWidget);
|
|
||||||
QPixmap pixmap(QCoreApplication::applicationDirPath() + "/images/alfa_icon.png");
|
|
||||||
icon->setPixmap(pixmap);
|
|
||||||
icon->setPixmap(pixmap.scaled(20, 20, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
||||||
icon->setStyleSheet(
|
|
||||||
"background-color: transparent;"
|
|
||||||
"padding:0 4px 0 0;"
|
|
||||||
);
|
|
||||||
icon->setMinimumSize(24, 20);
|
|
||||||
tableWidget->setCellWidget(k, 0, icon);
|
|
||||||
|
|
||||||
tableWidget->setItem(k, 1, createTableWidget(app.name == "rshb" ? "Россельхозбанк" : app.name));
|
|
||||||
tableWidget->setItem(k, 2, createTableWidget(app.pinCode));
|
|
||||||
tableWidget->setItem(k, 3, createTableWidget(app.status));
|
|
||||||
|
|
||||||
QTableWidgetItem *item = new QTableWidgetItem(app.comment);
|
|
||||||
item->setTextAlignment(Qt::AlignLeft | Qt::AlignTop);
|
|
||||||
item->setFlags(item->flags() & ~Qt::ItemIsEditable);
|
|
||||||
item->setSizeHint(QSize(100, app.comment.length() / 2)); // Минимальная высота ячейки для переноса
|
|
||||||
tableWidget->setItem(k, 4, item);
|
|
||||||
|
|
||||||
// auto *deleteBtnItem = new QTableWidgetItem;
|
|
||||||
// tableWidget->setItem(k, 4, deleteBtnItem);
|
|
||||||
auto *deleteBtn = new QPushButton(" Удалить ", tableWidget);
|
|
||||||
deleteBtn->setProperty("btnClass", "delete");
|
|
||||||
// deleteBtn->setStyleSheet("padding: 4px 8px;");
|
|
||||||
// deleteBtn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
|
||||||
tableWidget->setCellWidget(k, 5, deleteBtn);
|
|
||||||
|
|
||||||
connect(deleteBtn, &QPushButton::clicked, this, [this, app]() {
|
|
||||||
QMessageBox msgBox(this);
|
|
||||||
msgBox.setWindowTitle(tr("Подтверждение"));
|
|
||||||
msgBox.setText("Точно удалить все данные банка: " + app.name + "?");
|
|
||||||
// добавляем свои кнопки с нужными надписями
|
|
||||||
QPushButton *deleteBtn = msgBox.addButton(tr("Удалить"), QMessageBox::AcceptRole);
|
|
||||||
QPushButton *closeBtn = msgBox.addButton(tr("Отмена"), QMessageBox::RejectRole);
|
|
||||||
// по умолчанию фокус на «Закрыть»
|
|
||||||
msgBox.setDefaultButton(closeBtn);
|
|
||||||
// делаем окно модальным и блокируем всё остальное
|
|
||||||
msgBox.setModal(true);
|
|
||||||
msgBox.exec(); // <-- блокирует до закрытия сообщения
|
|
||||||
|
|
||||||
if (msgBox.clickedButton() == deleteBtn) {
|
|
||||||
// тут выполняем удаление
|
|
||||||
} else {
|
|
||||||
// просто закрыли — ничего не делаем
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// auto *editBtn = new QTableWidgetItem;
|
|
||||||
// tableWidget->setItem(k, 5, editBtn);
|
|
||||||
auto *btn = new QPushButton(" Редактировать ", tableWidget);
|
|
||||||
btn->setProperty("btnClass", "edit");
|
|
||||||
// btn->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Fixed);
|
|
||||||
tableWidget->setCellWidget(k, 6, btn);
|
|
||||||
connect(btn, &QPushButton::clicked, this, [this]() {
|
|
||||||
BankEditDialog dlg(this);
|
|
||||||
if (dlg.exec() == QDialog::Accepted) {
|
|
||||||
// Пользователь нажал «OK»
|
|
||||||
QString v1 = dlg.firstValue();
|
|
||||||
QString v2 = dlg.secondValue();
|
|
||||||
bool on = dlg.toggled();
|
|
||||||
// … обработка значений …
|
|
||||||
} else {
|
|
||||||
// Пользователь нажал «Cancel» или закрыл окно
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
layout->addWidget(tableWidget);
|
layout->addWidget(tableWidget);
|
||||||
tableWidget->resizeRowsToContents();
|
tableWidget->resizeRowsToContents();
|
||||||
|
|
||||||
|
|||||||
@ -13,4 +13,6 @@ private:
|
|||||||
QString m_deviceName;
|
QString m_deviceName;
|
||||||
|
|
||||||
QTableWidgetItem *createTableWidget(const QString &text);
|
QTableWidgetItem *createTableWidget(const QString &text);
|
||||||
|
|
||||||
|
void reloadContent(QTableWidget *tableWidget);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -6,10 +6,139 @@
|
|||||||
#include <QPushButton>
|
#include <QPushButton>
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
|
#include <QPropertyAnimation>
|
||||||
|
#include <QGraphicsScene>
|
||||||
#include "bank/BankSettingsWindow.h"
|
#include "bank/BankSettingsWindow.h"
|
||||||
|
#include "dao/ApplicationDAO.h"
|
||||||
|
#include "db/ApplicationInfo.h"
|
||||||
|
|
||||||
|
|
||||||
DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(parent) {
|
QString getColorByStatus(const ApplicationInfo &app) {
|
||||||
|
if (!app.install) {
|
||||||
|
return "black";
|
||||||
|
}
|
||||||
|
if (app.status == "active") {
|
||||||
|
if (!app.pinCodeChecked) {
|
||||||
|
return "orange";
|
||||||
|
}
|
||||||
|
return "green";
|
||||||
|
} else if (app.status == "off") {
|
||||||
|
return "red";
|
||||||
|
} else {
|
||||||
|
return "red";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceWidget::buildBankRow(const ApplicationInfo &app, QWidget *rowWidget, QWidget *parent) {
|
||||||
|
rowWidget->setContentsMargins(0, 0, 0, 0);
|
||||||
|
rowWidget->setStyleSheet(
|
||||||
|
"background-color: rgba(0, 0, 0, 0.6);" // полупрозрачный фон
|
||||||
|
"border-radius: 4px;" // скруглённые углы, если нужно
|
||||||
|
);
|
||||||
|
|
||||||
|
// вешаем на контейнер ваш QHBoxLayout
|
||||||
|
auto *hbox = new QHBoxLayout(rowWidget);
|
||||||
|
hbox->setContentsMargins(2, 1, 1, 1);
|
||||||
|
hbox->setSpacing(0);
|
||||||
|
|
||||||
|
auto *icon = new QLabel(parent);
|
||||||
|
QPixmap pixmap(QString("%1/images/%2_icon.png").arg(QCoreApplication::applicationDirPath(), app.code));
|
||||||
|
icon->setPixmap(pixmap);
|
||||||
|
icon->setPixmap(pixmap.scaled(18, 18, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
||||||
|
icon->setStyleSheet(
|
||||||
|
"background-color: transparent;"
|
||||||
|
"margin: 0px;"
|
||||||
|
);
|
||||||
|
|
||||||
|
|
||||||
|
auto *label = new QLabel(app.name, parent);
|
||||||
|
label->setStyleSheet(
|
||||||
|
"background-color: transparent;"
|
||||||
|
"margin: 0px;"
|
||||||
|
"font-size: 10px;"
|
||||||
|
"color: white;"
|
||||||
|
);
|
||||||
|
|
||||||
|
auto *circle = new QLabel(parent);
|
||||||
|
circle->setFixedSize(12, 12);
|
||||||
|
const QString color = getColorByStatus(app);
|
||||||
|
circle->setStyleSheet(
|
||||||
|
"margin: 0px;"
|
||||||
|
"background-color: " +color + "; "
|
||||||
|
"border-radius: 6px;"
|
||||||
|
);
|
||||||
|
|
||||||
|
// auto *btn1 = new QPushButton(parent);
|
||||||
|
// bool turnOn = app.pinCodeChecked && app.status == "active";
|
||||||
|
// QIcon btnIcon(QCoreApplication::applicationDirPath() + (turnOn ? "/images/turn_off.png" : "/images/turn_on.png"));
|
||||||
|
// btn1->setIcon(btnIcon);
|
||||||
|
// btn1->setIconSize(QSize(16, 16));
|
||||||
|
// btn1->setFlat(true);
|
||||||
|
// btn1->setStyleSheet(
|
||||||
|
// "margin: 0px;"
|
||||||
|
// "background-color: transparent;"
|
||||||
|
// "border: none;"
|
||||||
|
// );
|
||||||
|
|
||||||
|
auto *btn2 = new QPushButton(parent);
|
||||||
|
QIcon btn2Icon(QCoreApplication::applicationDirPath() + "/images/cogwheel.png");
|
||||||
|
btn2->setIcon(btn2Icon);
|
||||||
|
btn2->setIconSize(QSize(16, 16));
|
||||||
|
btn2->setFlat(true);
|
||||||
|
btn2->setStyleSheet(
|
||||||
|
"margin: 0px;"
|
||||||
|
"background-color: transparent;"
|
||||||
|
"border: none;"
|
||||||
|
);
|
||||||
|
// в конструкторе окна или сразу после создания btn2:
|
||||||
|
connect(btn2, &QPushButton::pressed, this, [btn2] {
|
||||||
|
auto anim = new QPropertyAnimation(btn2, "geometry");
|
||||||
|
anim->setDuration(120);
|
||||||
|
QRect orig = btn2->geometry();
|
||||||
|
QRect smaller = orig.adjusted(orig.width() * 0.05,
|
||||||
|
orig.height() * 0.05,
|
||||||
|
-orig.width() * 0.05,
|
||||||
|
-orig.height() * 0.05);
|
||||||
|
anim->setStartValue(orig);
|
||||||
|
anim->setKeyValueAt(0.5, smaller);
|
||||||
|
anim->setEndValue(orig);
|
||||||
|
anim->setEasingCurve(QEasingCurve::InOutQuad);
|
||||||
|
anim->start(QAbstractAnimation::DeleteWhenStopped);
|
||||||
|
});
|
||||||
|
|
||||||
|
setOpenBanksSettings(btn2);
|
||||||
|
|
||||||
|
// собираем
|
||||||
|
hbox->addWidget(icon);
|
||||||
|
hbox->addWidget(label,1);
|
||||||
|
hbox->addWidget(circle);
|
||||||
|
// hbox->addWidget(btn1);
|
||||||
|
hbox->addWidget(btn2);
|
||||||
|
|
||||||
|
// hbox->addStretch();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeviceWidget::setOpenBanksSettings(QPushButton *button) {
|
||||||
|
QString deviceId = m_device.id;
|
||||||
|
QString deviceName = m_device.name;
|
||||||
|
connect(button, &QPushButton::clicked, this, [deviceId, deviceName]() {
|
||||||
|
static QPointer<BankSettingsWindow> bankSettingsWindow;
|
||||||
|
|
||||||
|
// Если окна нет (либо ещё не было создано, либо уже удалилось) — создаём заново
|
||||||
|
if (!bankSettingsWindow) {
|
||||||
|
bankSettingsWindow = new BankSettingsWindow(nullptr, deviceId, deviceName);
|
||||||
|
bankSettingsWindow->setWindowFlags(bankSettingsWindow->windowFlags() | Qt::Window);
|
||||||
|
bankSettingsWindow->setAttribute(Qt::WA_DeleteOnClose);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Показать/поднять/активировать
|
||||||
|
bankSettingsWindow->show();
|
||||||
|
bankSettingsWindow->raise();
|
||||||
|
bankSettingsWindow->activateWindow();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(parent), m_device(device) {
|
||||||
auto *layout = new QGridLayout(this);
|
auto *layout = new QGridLayout(this);
|
||||||
layout->setContentsMargins(2, 2, 2, 2);
|
layout->setContentsMargins(2, 2, 2, 2);
|
||||||
layout->setSpacing(0);
|
layout->setSpacing(0);
|
||||||
@ -44,92 +173,29 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
|||||||
layout->addWidget(batteryLabel, 0, 0, Qt::AlignTop | Qt::AlignRight);
|
layout->addWidget(batteryLabel, 0, 0, Qt::AlignTop | Qt::AlignRight);
|
||||||
|
|
||||||
|
|
||||||
auto *overlay = new QWidget(this);
|
auto *appsView = new QWidget(this);
|
||||||
overlay->setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
appsView->setAttribute(Qt::WA_TransparentForMouseEvents, false);
|
||||||
overlay->setStyleSheet("background: transparent;"); // сам фон прозрачный
|
appsView->setStyleSheet("background: transparent;");
|
||||||
// вертикальный лэйаут списка внутри overlay
|
|
||||||
auto *vbox = new QVBoxLayout(overlay);
|
|
||||||
vbox->setContentsMargins(2, 0, 0, 2);
|
|
||||||
vbox->setSpacing(2);
|
|
||||||
|
|
||||||
// пример трёх строк (можете генерировать их динамически)
|
auto *appsBoxLayout = new QVBoxLayout(appsView);
|
||||||
for (int i = 0; i < 3; ++i) {
|
appsBoxLayout->setContentsMargins(2, 0, 0, 2);
|
||||||
auto *rowWidget = new QWidget(overlay);
|
appsBoxLayout->setSpacing(2);
|
||||||
rowWidget->setContentsMargins(0, 0, 0, 0);
|
|
||||||
rowWidget->setStyleSheet(
|
|
||||||
"background-color: rgba(0, 0, 0, 0.6);" // полупрозрачный фон
|
|
||||||
"border-radius: 4px;" // скруглённые углы, если нужно
|
|
||||||
);
|
|
||||||
|
|
||||||
// вешаем на контейнер ваш QHBoxLayout
|
QList<ApplicationInfo> apps = ApplicationDAO::getApplicationsByDeviceId(device.id);
|
||||||
auto *hbox = new QHBoxLayout(rowWidget);
|
for (ApplicationInfo &app: apps) {
|
||||||
hbox->setContentsMargins(2, 1, 1, 1);
|
if (app.install) {
|
||||||
hbox->setSpacing(0);
|
auto *rowWidget = new QWidget(appsView);
|
||||||
|
buildBankRow(app, rowWidget, appsView);
|
||||||
auto *icon = new QLabel(overlay);
|
appsBoxLayout->addWidget(rowWidget, 1);
|
||||||
QPixmap pixmap(QCoreApplication::applicationDirPath() + "/images/alfa_icon.png");
|
}
|
||||||
icon->setPixmap(pixmap);
|
|
||||||
icon->setPixmap(pixmap.scaled(18, 18, Qt::KeepAspectRatio, Qt::SmoothTransformation));
|
|
||||||
icon->setStyleSheet(
|
|
||||||
"background-color: transparent;"
|
|
||||||
"margin: 0px;"
|
|
||||||
);
|
|
||||||
|
|
||||||
|
|
||||||
auto *label = new QLabel(QString("Банк %1").arg(i + 1), overlay);
|
|
||||||
label->setStyleSheet(
|
|
||||||
"background-color: transparent;"
|
|
||||||
"margin: 0px;"
|
|
||||||
"font-size: 10px;"
|
|
||||||
"color: white;"
|
|
||||||
);
|
|
||||||
|
|
||||||
auto *circle = new QLabel(overlay);
|
|
||||||
circle->setFixedSize(12, 12);
|
|
||||||
circle->setStyleSheet(
|
|
||||||
"margin: 0px;"
|
|
||||||
"background-color: red; "
|
|
||||||
"border-radius: 6px;"
|
|
||||||
);
|
|
||||||
|
|
||||||
auto *btn1 = new QPushButton(overlay);
|
|
||||||
QIcon btnIcon(QCoreApplication::applicationDirPath() + "/images/turn_on.png");
|
|
||||||
btn1->setIcon(btnIcon);
|
|
||||||
btn1->setIconSize(QSize(16, 16));
|
|
||||||
btn1->setFlat(true);
|
|
||||||
btn1->setStyleSheet(
|
|
||||||
"margin: 0px;"
|
|
||||||
"background-color: transparent;"
|
|
||||||
"border: none;"
|
|
||||||
);
|
|
||||||
|
|
||||||
auto *btn2 = new QPushButton(overlay);
|
|
||||||
QIcon btn2Icon(QCoreApplication::applicationDirPath() + "/images/cogwheel.png");
|
|
||||||
btn2->setIcon(btn2Icon);
|
|
||||||
btn2->setIconSize(QSize(16, 16));
|
|
||||||
btn2->setFlat(true);
|
|
||||||
btn2->setStyleSheet(
|
|
||||||
"margin: 0px;"
|
|
||||||
"background-color: transparent;"
|
|
||||||
"border: none;"
|
|
||||||
);
|
|
||||||
|
|
||||||
// собираем
|
|
||||||
hbox->addWidget(icon);
|
|
||||||
hbox->addWidget(label);
|
|
||||||
hbox->addWidget(circle);
|
|
||||||
hbox->addWidget(btn1);
|
|
||||||
hbox->addWidget(btn2);
|
|
||||||
|
|
||||||
hbox->addStretch();
|
|
||||||
vbox->addWidget(rowWidget);
|
|
||||||
}
|
}
|
||||||
vbox->addStretch();
|
// appsBoxLayout->addStretch();
|
||||||
|
|
||||||
|
|
||||||
// кладём overlay в ту же ячейку, что и картинку,
|
// кладём overlay в ту же ячейку, что и картинку,
|
||||||
// но выравниваем его по левому-нижнему углу
|
// но выравниваем его по левому-нижнему углу
|
||||||
layout->addWidget(overlay, 0, 0, Qt::AlignLeft | Qt::AlignBottom);
|
layout->setColumnStretch(0, 1);
|
||||||
|
layout->addWidget(appsView, 0, 0, Qt::AlignLeft | Qt::AlignBottom);
|
||||||
|
// layout->addWidget(appsView, 0, 0, Qt::AlignLeft); TODO так на всю ширину
|
||||||
|
|
||||||
auto *addBankBtn = new QPushButton(this);
|
auto *addBankBtn = new QPushButton(this);
|
||||||
addBankBtn->setMaximumWidth(300);
|
addBankBtn->setMaximumWidth(300);
|
||||||
@ -141,22 +207,6 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p
|
|||||||
"color: #000;"
|
"color: #000;"
|
||||||
);
|
);
|
||||||
|
|
||||||
QString deviceId = device.id;
|
setOpenBanksSettings(addBankBtn);
|
||||||
QString deviceName = device.name;
|
|
||||||
connect(addBankBtn, &QPushButton::clicked, this, [deviceId, deviceName]() {
|
|
||||||
static QPointer<BankSettingsWindow> bankSettingsWindow;
|
|
||||||
|
|
||||||
// Если окна нет (либо ещё не было создано, либо уже удалилось) — создаём заново
|
|
||||||
if (!bankSettingsWindow) {
|
|
||||||
bankSettingsWindow = new BankSettingsWindow(nullptr, deviceId, deviceName);
|
|
||||||
bankSettingsWindow->setWindowFlags(bankSettingsWindow->windowFlags() | Qt::Window);
|
|
||||||
bankSettingsWindow->setAttribute(Qt::WA_DeleteOnClose);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Показать/поднять/активировать
|
|
||||||
bankSettingsWindow->show();
|
|
||||||
bankSettingsWindow->raise();
|
|
||||||
bankSettingsWindow->activateWindow();
|
|
||||||
});
|
|
||||||
layout->addWidget(addBankBtn, 1, 0);
|
layout->addWidget(addBankBtn, 1, 0);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,9 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
#include <qpushbutton.h>
|
||||||
#include <qtablewidget.h>
|
#include <qtablewidget.h>
|
||||||
#include <QWidget>
|
#include <QWidget>
|
||||||
|
|
||||||
|
#include "db/ApplicationInfo.h"
|
||||||
#include "db/DeviceInfo.h"
|
#include "db/DeviceInfo.h"
|
||||||
|
|
||||||
class DeviceWidget final : public QWidget {
|
class DeviceWidget final : public QWidget {
|
||||||
@ -8,4 +11,11 @@ class DeviceWidget final : public QWidget {
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeviceWidget(const DeviceInfo &device, QWidget *parent = nullptr);
|
explicit DeviceWidget(const DeviceInfo &device, QWidget *parent = nullptr);
|
||||||
|
|
||||||
|
private:
|
||||||
|
const DeviceInfo m_device;
|
||||||
|
|
||||||
|
void buildBankRow(const ApplicationInfo &app, QWidget *rowWidget, QWidget *parent);
|
||||||
|
|
||||||
|
void setOpenBanksSettings(QPushButton *button);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -4,45 +4,46 @@
|
|||||||
#include <QLineEdit>
|
#include <QLineEdit>
|
||||||
#include <QCheckBox>
|
#include <QCheckBox>
|
||||||
#include <QDialogButtonBox>
|
#include <QDialogButtonBox>
|
||||||
|
#include <qtextedit.h>
|
||||||
|
#include "db/ApplicationInfo.h"
|
||||||
|
|
||||||
// 1) Определяем диалог
|
class BankEditDialog final : public QDialog {
|
||||||
class BankEditDialog : public QDialog {
|
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit BankEditDialog(QWidget *parent = nullptr) : QDialog(parent) {
|
explicit BankEditDialog(const ApplicationInfo &app, QWidget *parent = nullptr) : QDialog(parent) {
|
||||||
|
setWindowTitle("Настройка " + app.name);
|
||||||
|
setModal(true);
|
||||||
|
|
||||||
setWindowTitle(tr("Введите данные"));
|
|
||||||
setModal(true); // делаем его модальным
|
|
||||||
|
|
||||||
// 2) Форма с полями
|
|
||||||
auto *form = new QFormLayout(this);
|
auto *form = new QFormLayout(this);
|
||||||
firstEdit = new QLineEdit(this);
|
firstEdit = new QLineEdit(this);
|
||||||
secondEdit = new QLineEdit(this);
|
firstEdit->setText(app.pinCode);
|
||||||
toggle = new QCheckBox(tr("Включено"), this);
|
secondEdit = new QTextEdit(this);
|
||||||
|
secondEdit->setMaximumHeight(100);
|
||||||
|
secondEdit->setPlainText(app.comment);
|
||||||
|
toggle = new QCheckBox(tr("Включен"), this);
|
||||||
|
toggle->setChecked(app.status == "active");
|
||||||
|
|
||||||
form->addRow(tr("Первое поле:"), firstEdit);
|
form->addRow(tr("Пин-код:"), firstEdit);
|
||||||
form->addRow(tr("Второе поле:"), secondEdit);
|
form->addRow(tr("Комментарий:"), secondEdit);
|
||||||
form->addRow(QString(), toggle);
|
form->addRow(QString(), toggle);
|
||||||
|
|
||||||
// 3) Кнопки OK / Cancel
|
auto *buttons = new QDialogButtonBox(Qt::Horizontal, this);
|
||||||
auto *buttons = new QDialogButtonBox(
|
buttons->addButton(tr("Сохранить"), QDialogButtonBox::AcceptRole);
|
||||||
QDialogButtonBox::Ok | QDialogButtonBox::Cancel,
|
buttons->addButton(tr("Отмена"), QDialogButtonBox::RejectRole);
|
||||||
Qt::Horizontal,
|
|
||||||
this
|
|
||||||
);
|
|
||||||
form->addRow(buttons);
|
form->addRow(buttons);
|
||||||
|
|
||||||
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept);
|
||||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
QString firstValue() const { return firstEdit->text(); }
|
QString firstValue() const { return firstEdit->text(); }
|
||||||
QString secondValue() const { return secondEdit->text(); }
|
QString secondValue() const { return secondEdit->toPlainText(); }
|
||||||
bool toggled() const { return toggle->isChecked(); }
|
bool toggled() const { return toggle->isChecked(); }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QLineEdit *firstEdit;
|
QLineEdit *firstEdit;
|
||||||
QLineEdit *secondEdit;
|
QTextEdit *secondEdit;
|
||||||
QCheckBox *toggle;
|
QCheckBox *toggle;
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user