diff --git a/android/adb/AdbUtils.cpp b/android/adb/AdbUtils.cpp index daa9c90..d60ac8e 100644 --- a/android/adb/AdbUtils.cpp +++ b/android/adb/AdbUtils.cpp @@ -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 'rm /sdcard/uidump.xml'" - // ).arg(deviceId, QString::number(idx)); + // ).arg(deviceId, QString::number(idx)); ).arg(deviceId); process.start("sh", {"-c", xml}); process.waitForFinished(); @@ -221,3 +221,27 @@ bool AdbUtils::goBack(const QString &deviceId) { process.waitForFinished(); return process.exitStatus() == QProcess::NormalExit && process.exitCode() == 0; } + +QSet 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 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; +} diff --git a/android/adb/AdbUtils.h b/android/adb/AdbUtils.h index 2550cef..dd93b73 100644 --- a/android/adb/AdbUtils.h +++ b/android/adb/AdbUtils.h @@ -34,4 +34,6 @@ public: static bool inputText(const QString &deviceId, const QString &text); static bool goBack(const QString &deviceId); + + static QSet getListApps(const QString &deviceId); }; diff --git a/android/rshb/PayByPhoneScript.cpp b/android/rshb/PayByPhoneScript.cpp index 7c8f55e..63bb3b5 100644 --- a/android/rshb/PayByPhoneScript.cpp +++ b/android/rshb/PayByPhoneScript.cpp @@ -262,7 +262,7 @@ void PayByPhoneScript::doStart() { const DeviceInfo device = DeviceDAO::getDeviceById(m_account.deviceId); 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...."; qDebug() << m_error; emit finishedWithResult(m_error); diff --git a/automation_script/AdbUtils.cpp b/automation_script/AdbUtils.cpp deleted file mode 100644 index ebf0c65..0000000 --- a/automation_script/AdbUtils.cpp +++ /dev/null @@ -1,90 +0,0 @@ -#include "AdbUtils.h" -#include -#include -#include -#include - -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; -// } diff --git a/automation_script/AdbUtils.h b/automation_script/AdbUtils.h deleted file mode 100644 index 20a3697..0000000 --- a/automation_script/AdbUtils.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef ADBUTILS_H -#define ADBUTILS_H - -#include - -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 diff --git a/automation_script/Script.cpp b/automation_script/Script.cpp deleted file mode 100644 index a1a470f..0000000 --- a/automation_script/Script.cpp +++ /dev/null @@ -1,504 +0,0 @@ -// -// Created by AmirS on 24.04.2025. -// - -#include "Script.h" -#include "AdbUtils.h" -#include "XmlParser.h" -#include "QDebug" -#include - -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 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 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& 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 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 &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 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& 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 &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 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 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 &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 &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 &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 &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 &ui) { -// for (const auto &el: ui) { -// if (el.text == "Оплатить") { -// return el; -// } -// } -// return {}; -// } - -void Script::stop() { - m_running = false; -} diff --git a/automation_script/Script.h b/automation_script/Script.h deleted file mode 100644 index 0f55ec8..0000000 --- a/automation_script/Script.h +++ /dev/null @@ -1,111 +0,0 @@ -// -// Created by Misterio on 24.04.2025. -// - -#ifndef SCRIPT_H -#define SCRIPT_H -#include -#include -#include "AdbUtils.h" -#include "XmlParser.h" -#include "auto_payment/UiElement.h" -#include -#include - -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 &ui); - // - // // Проверка, что перед нами экран ввода ПИН - // bool isPinScreen(const QList &ui); - // - // // Получение списка кнопок с цифрами для ввода ПИН - // std::vector getPinButtons(const QList &ui); - // - // // Тап по элементу с текстом - // void tapByText(const QList &ui, const QString &text); - // - // // Тап по полю ввода - // void tapInputField(const QList &ui, const QString &fieldText); - // - // // Тап по кнопке "Далее" и её вариациям - // void tapNextButton(const QList &ui); - // - // // Обработка экрана с пост-логин баннерами или диалогами - // bool handlePostLoginScreen(const QList &ui); - // - // // Проверка, что перед нами главный экран - // bool isMainScreen(const QList &ui); - // - // // Тап по главному экрану - // void tapMainScreen(const QList &ui); - // - // // Функция для поиска карты по последним 4 цифрам - // UiElement findCardByLast4Digits(const QList &ui, const QString &last4digits); - // - // // Поиск кнопки "Оплатить" на экране - // UiElement findPayButton(const QList &ui); - -private: - enum class State { - HandlingSecurityBanner, - WaitingForPinInput, - ClosingBanner, - OnMainScreen, - SearchingCard, - OnCardDetails, - OnTransferScreen, - Idle - }; - - // Основной цикл скрипта - void processUiLoop(); - - // void handleState(const QList &elements); - - static void handleSecurityBanner(const QList& elements); - - static void inputPinCode(const QString &pinCode); - - void handleClosingBanner(const QList &elements); - - static void swipeToFindElement(const QString &searchText); - - static void handleCardDetailsScreen(const QList &elements); - - // void handleTransferScreen(const QList &elements); - - static void inputAmount(const QString &amount); - - // Утилиты для работы с ADB - AdbUtils adbUtils; - // Парсер XML UI дампов мобильного устройства - XmlParser xmlParser; - // Текущее состояние скрипта автоматизации - State currentState = State::Idle; -}; - -#endif //SCRIPT_H diff --git a/automation_script/XmlParser.cpp b/automation_script/XmlParser.cpp deleted file mode 100644 index 0418b05..0000000 --- a/automation_script/XmlParser.cpp +++ /dev/null @@ -1,152 +0,0 @@ -// -// Created by AmirS on 25.04.2025. -// - -#include "XmlParser.h" -#include -#include -#include - -XmlParser::XmlParser(QObject *parent) : QObject(parent) { -} - -QList XmlParser::parseUiDumpUsingQXml(const QString &filepath) { - QFile file(filepath); - QList 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& 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 &elements) { - for (const UiElement &element: elements) { - // Проверка на наличие текста для ввода PIN - if (element.text().contains("Введите ПИН")) { - return true; - } - } - return false; -} - -bool XmlParser::isBannerOrDialog(const QList &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 &elements) { - for (const UiElement &element: elements) { - // Проверка на кликабельную кнопку "Все продукты" - if (element.text().contains("Все продукты") && element.isClickable()) { - return true; - } - } - return false; -} - -bool XmlParser::isDetailCardScreen(const QList &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; -} diff --git a/automation_script/XmlParser.h b/automation_script/XmlParser.h deleted file mode 100644 index cb744e1..0000000 --- a/automation_script/XmlParser.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef XMLPARSER_H -#define XMLPARSER_H - -#include -#include -#include -#include "auto_payment/UiElement.h" - -class XmlParser final : public QObject -{ - Q_OBJECT -public: - explicit XmlParser(QObject *parent = nullptr); - - static QList parseUiDumpUsingQXml(const QString &filepath); - static bool isSecurityBanner(const QList& elements); - static bool isPinCodeScreen(const QList& elements); - static bool isBannerOrDialog(const QList& elements); - static bool isMainScreen(const QList& elements); - static bool isDetailCardScreen(const QList& elements); -private: - static QList extractElementsFromXml(QXmlStreamReader &xmlReader); -}; - -#endif // XMLPARSER_H diff --git a/config.ini b/config.ini index 551c814..1c3d1b0 100644 --- a/config.ini +++ b/config.ini @@ -2,12 +2,21 @@ varsion=0.0.1 [apps] -list=rshb +list=rshb, alfabank, santanderbank [rshb] name=Росcельхозбанк 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] appInfoUpdate="http://localhost:9999/api/v1/app/update" accountUpdate=http://localhost:9999/api/v1/account/update diff --git a/database/app_data.db b/database/app_data.db index 41dbd73..f0468ea 100644 Binary files a/database/app_data.db and b/database/app_data.db differ diff --git a/database/dao/ApplicationDAO.cpp b/database/dao/ApplicationDAO.cpp index 1f29cb7..2b0eb17 100644 --- a/database/dao/ApplicationDAO.cpp +++ b/database/dao/ApplicationDAO.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "ApplicationDAO.h" #include "DatabaseManager.h" #include "db/ApplicationInfo.h" @@ -9,27 +10,31 @@ bool ApplicationDAO::upsertApplication(const ApplicationInfo &info) { QSqlQuery query(DatabaseManager::instance().database()); query.prepare(R"( - INSERT INTO applications (id, device_id, pin_code, name, package, status, comment, image) - VALUES (:id, :device_id, :pin_code, :name, :package, :status, :comment, :image) - ON CONFLICT(device_id, name) + INSERT INTO applications (id, device_id, pin_code, name, code, package, status, comment, install, pin_code_checked) + VALUES (:id, :device_id, :pin_code, :name, :code, :package, :status, :comment, :install, :pin_code_checked) + ON CONFLICT(device_id, code) DO UPDATE SET device_id = excluded.device_id, pin_code = excluded.pin_code, name = excluded.name, + code = excluded.code, package = excluded.package, status = excluded.status, comment = excluded.comment, - image = excluded.image + install = excluded.install, + pin_code_checked = excluded.pin_code_checked, )"); query.bindValue(":id", info.id); query.bindValue(":device_id", info.deviceId); query.bindValue(":pin_code", info.pinCode); query.bindValue(":name", info.name); + query.bindValue(":code", info.code); query.bindValue(":package", info.package); query.bindValue(":status", info.status); 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()) { qWarning() << "Ошибка при вставке/обновлении application:" << query.lastError().text(); @@ -55,35 +60,63 @@ bool ApplicationDAO::updatePinCode(const QString &appId, const QString &pinCode) qWarning() << "Ошибка при обновлении PIN-кода приложения:" << query.lastError().text(); 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; } ApplicationInfo parseApplication(const QSqlQuery &query) { ApplicationInfo app; - app.id = query.value("id").toString(); + app.id = query.value("id").toInt(); app.deviceId = query.value("device_id").toString(); app.pinCode = query.value("pin_code").toString(); + app.code = query.value("code").toString(); app.name = query.value("name").toString(); app.package = query.value("package").toString(); app.status = query.value("status").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; } -ApplicationInfo ApplicationDAO::getApplication(const QString &deviceId, const QString &appName) { +ApplicationInfo ApplicationDAO::getApplication(const QString &deviceId, const QString &appCode) { QSqlQuery query(DatabaseManager::instance().database()); 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 - WHERE device_id = :device_id AND name = :name + WHERE device_id = :device_id AND code = :code LIMIT 1 )"); query.bindValue(":device_id", deviceId); - query.bindValue(":name", appName); + query.bindValue(":code", appCode); if (!query.exec()) { qWarning() << "Ошибка при получении приложений по deviceId:" << query.lastError().text(); @@ -100,7 +133,7 @@ QList ApplicationDAO::getApplicationsByDeviceId(const QString & QSqlQuery query(DatabaseManager::instance().database()); QList list; 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 WHERE device_id = :device_id )"); @@ -118,3 +151,38 @@ QList ApplicationDAO::getApplicationsByDeviceId(const QString & return list; } + +bool ApplicationDAO::checkInstalledApps(const QString &deviceId, const QSet &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; +} diff --git a/database/dao/ApplicationDAO.h b/database/dao/ApplicationDAO.h index 6d3ba5c..873f92d 100644 --- a/database/dao/ApplicationDAO.h +++ b/database/dao/ApplicationDAO.h @@ -8,7 +8,12 @@ public: [[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 getApplicationsByDeviceId(const QString &deviceId); + + [[nodiscard]] static bool checkInstalledApps(const QString &deviceId, const QSet &apps); }; diff --git a/images/alfa_icon.png b/images/alfabank_icon.png similarity index 100% rename from images/alfa_icon.png rename to images/alfabank_icon.png diff --git a/images/rshb_icon.png b/images/rshb_icon.png new file mode 100644 index 0000000..a5124c1 Binary files /dev/null and b/images/rshb_icon.png differ diff --git a/images/santanderbank_icon.png b/images/santanderbank_icon.png new file mode 100644 index 0000000..4b0d927 Binary files /dev/null and b/images/santanderbank_icon.png differ diff --git a/main.cpp b/main.cpp index 65d1084..e1dba74 100644 --- a/main.cpp +++ b/main.cpp @@ -8,7 +8,6 @@ #include "database/dao/CommonDataDAO.h" #include "db/AccountInfoScreener.h" #include "services/DeviceScreener.h" -#include "automation_script/Script.h" #include #include @@ -22,7 +21,7 @@ #include "rshb/HomeScreenScript.h" #include "rshb/PayByPhoneScript.h" -void setupWorkerAndThread(QCoreApplication &app) { +void startDeviceScreener(QCoreApplication &app) { auto *thread = new QThread; auto *deviceScreenerWorker = new DeviceScreener; @@ -222,7 +221,7 @@ int main(int argc, char *argv[]) { // QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater); // paymentThread->start(); // - // setupWorkerAndThread(app); + startDeviceScreener(app); MainWindow window; window.show(); diff --git a/migrations.sql b/migrations.sql index 3b89184..d8cc374 100644 --- a/migrations.sql +++ b/migrations.sql @@ -14,16 +14,18 @@ CREATE TABLE IF NOT EXISTS devices CREATE TABLE IF NOT EXISTS applications ( - id TEXT PRIMARY KEY, - device_id TEXT, - pin_code TEXT, - name TEXT, - package TEXT, - status TEXT, - comment TEXT, - image BLOB, + id INTEGER PRIMARY KEY AUTOINCREMENT, + device_id TEXT, + pin_code TEXT, + pin_code_checked TEXT, + name TEXT, + code TEXT, + package TEXT, + status TEXT, + install TEXT, + comment TEXT, FOREIGN KEY (device_id) REFERENCES devices (id) ON DELETE CASCADE, - UNIQUE (device_id, name) + UNIQUE (device_id, code) ); CREATE TABLE IF NOT EXISTS accounts diff --git a/models/auto_payment/UiElement.cpp b/models/auto_payment/UiElement.cpp deleted file mode 100644 index 153090e..0000000 --- a/models/auto_payment/UiElement.cpp +++ /dev/null @@ -1,47 +0,0 @@ -// -// Created by AmirS on 26.04.2025. -// - -#include "UiElement.h" -#include -#include -#include - -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); -} diff --git a/models/auto_payment/UiElement.h b/models/auto_payment/UiElement.h deleted file mode 100644 index 275c5f1..0000000 --- a/models/auto_payment/UiElement.h +++ /dev/null @@ -1,32 +0,0 @@ -// -// Created by AmirS on 26.04.2025. -// - -#ifndef UIELEMENT_H -#define UIELEMENT_H - -#include - -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 diff --git a/models/db/AccountInfo.h b/models/db/AccountInfo.h index 25916b3..817bc16 100644 --- a/models/db/AccountInfo.h +++ b/models/db/AccountInfo.h @@ -23,7 +23,7 @@ inline AccountStatus accountStatusFromString(const QString &str) { struct AccountInfo { int id = -1; - QString deviceId; + QString deviceId; // code QString appName; QString lastNumbers; double amount = 0.0; diff --git a/models/db/ApplicationInfo.h b/models/db/ApplicationInfo.h index c545f26..e7cf8ca 100644 --- a/models/db/ApplicationInfo.h +++ b/models/db/ApplicationInfo.h @@ -5,13 +5,15 @@ #include struct ApplicationInfo { - QString id; + int id = -1; + bool install = false; + bool pinCodeChecked = false; QString deviceId; QString pinCode; + QString code; QString name; QString package; QString status; QString comment; - QByteArray image; QDateTime updateTime; }; diff --git a/services/DeviceScreener.cpp b/services/DeviceScreener.cpp index 39ae889..1b12848 100644 --- a/services/DeviceScreener.cpp +++ b/services/DeviceScreener.cpp @@ -15,6 +15,9 @@ #include #include +#include "adb/AdbUtils.h" +#include "dao/ApplicationDAO.h" + DeviceScreener::DeviceScreener(QObject *parent) : QObject(parent) { const QSettings settings("config.ini", QSettings::IniFormat); m_urlAppUpdate = settings.value("net/appInfoUpdate").toString(); @@ -95,6 +98,7 @@ void DeviceScreener::postDevicesData(const QList &devices) { } void DeviceScreener::start() { + QMap bankChecked; while (m_running) { QList > devices = readAdbDevices(); QStringList onlineIds; @@ -103,14 +107,15 @@ void DeviceScreener::start() { DeviceInfo device; device.id = id; - if (status == "device") { onlineIds << device.id; - device = readDeviceInfo(id); + const bool isChecked = bankChecked.contains(id); + device = readDeviceInfo(id, isChecked); device.image = takeScreenshot(id); if (!DeviceDAO::updateImage(id, device.image)) { qDebug() << "Cant update screenshot"; } + bankChecked[id] = true; } device.status = status; device.updateTime = QDateTime::currentDateTime(); @@ -201,7 +206,7 @@ QList > DeviceScreener::readAdbDevices() { static const QRegularExpression screenRegex(R"(Physical size: (\d+)x(\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; info.id = deviceId; info.screenWidth = 0; @@ -233,6 +238,15 @@ DeviceInfo DeviceScreener::readDeviceInfo(const QString &deviceId) { info.battery = match.captured(1).toInt(); } + if (!isChecked) { + QSet apps = AdbUtils::getListApps(deviceId); + if (!ApplicationDAO::checkInstalledApps(deviceId, apps)) { + qWarning() << "Cant check installed apps"; + } else { + qDebug() << "Apps checked: " + deviceId; + } + } + info.updateTime = QDateTime::currentDateTime(); return info; } diff --git a/services/DeviceScreener.h b/services/DeviceScreener.h index b26688a..4023972 100644 --- a/services/DeviceScreener.h +++ b/services/DeviceScreener.h @@ -27,7 +27,7 @@ private: static QByteArray takeScreenshot(const QString &deviceId); - static DeviceInfo readDeviceInfo(const QString &deviceId); + static DeviceInfo readDeviceInfo(const QString &deviceId, bool isChecked); void postDevicesData(const QList &devices); }; diff --git a/views/bank/BankSettingsWindow.cpp b/views/bank/BankSettingsWindow.cpp index d91dc4e..f2f2e49 100644 --- a/views/bank/BankSettingsWindow.cpp +++ b/views/bank/BankSettingsWindow.cpp @@ -24,6 +24,180 @@ QTableWidgetItem *BankSettingsWindow::createTableWidget(const QString &text) { 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 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) : QWidget(parent, Qt::Window @@ -34,10 +208,10 @@ BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QStrin m_deviceId(std::move(deviceId)), m_deviceName(std::move(deviceName) ) { - setWindowTitle("Настройки банка [" + m_deviceName + "]"); - setMinimumSize(820, 400); - setMaximumSize(820, 400); - resize(820, 400); + setWindowTitle("Настройки банков [" + m_deviceName + "]"); + setMinimumSize(800, 400); + setMaximumSize(1000, 800); + resize(800, 400); auto *mainLayout = new QVBoxLayout(this); @@ -55,7 +229,6 @@ BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QStrin scrollArea->setStyleSheet("border: none;"); // Убираем рамку layout->setAlignment(Qt::AlignTop); - QList applications = ApplicationDAO::getApplicationsByDeviceId(m_deviceId); QTableWidget *tableWidget = new QTableWidget(this); tableWidget->setSelectionMode(QAbstractItemView::NoSelection); @@ -95,6 +268,9 @@ BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QStrin "QPushButton[btnClass='delete'] {" "background-color: red;" "}" + "QPushButton[btnClass='pincode'] {" + "background-color: orange;" + "}" "QPushButton[btnClass='edit'] {" "background-color: green;" "}" @@ -105,108 +281,11 @@ BankSettingsWindow::BankSettingsWindow(QWidget *parent, QString deviceId, QStrin "background-color: blue;" "}" ); - tableWidget->setRowCount(applications.length()); - tableWidget->setColumnCount(7); - tableWidget->setHorizontalHeaderLabels({ - "", - "Имя", - "Пин-код", - "Статус", - "Комментарий", - "", - "" - }); - tableWidget->setWordWrap(true); - tableWidget->setTextElideMode(Qt::ElideNone); + tableWidget->clearContents(); + 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); tableWidget->resizeRowsToContents(); diff --git a/views/bank/BankSettingsWindow.h b/views/bank/BankSettingsWindow.h index 1600520..42cfe0f 100644 --- a/views/bank/BankSettingsWindow.h +++ b/views/bank/BankSettingsWindow.h @@ -13,4 +13,6 @@ private: QString m_deviceName; QTableWidgetItem *createTableWidget(const QString &text); + + void reloadContent(QTableWidget *tableWidget); }; diff --git a/views/device/DeviceWidget.cpp b/views/device/DeviceWidget.cpp index 1a6380c..ec2ea67 100644 --- a/views/device/DeviceWidget.cpp +++ b/views/device/DeviceWidget.cpp @@ -6,10 +6,139 @@ #include #include #include +#include +#include #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; + + // Если окна нет (либо ещё не было создано, либо уже удалилось) — создаём заново + 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); layout->setContentsMargins(2, 2, 2, 2); 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); - auto *overlay = new QWidget(this); - overlay->setAttribute(Qt::WA_TransparentForMouseEvents, false); - overlay->setStyleSheet("background: transparent;"); // сам фон прозрачный - // вертикальный лэйаут списка внутри overlay - auto *vbox = new QVBoxLayout(overlay); - vbox->setContentsMargins(2, 0, 0, 2); - vbox->setSpacing(2); + auto *appsView = new QWidget(this); + appsView->setAttribute(Qt::WA_TransparentForMouseEvents, false); + appsView->setStyleSheet("background: transparent;"); - // пример трёх строк (можете генерировать их динамически) - for (int i = 0; i < 3; ++i) { - auto *rowWidget = new QWidget(overlay); - rowWidget->setContentsMargins(0, 0, 0, 0); - rowWidget->setStyleSheet( - "background-color: rgba(0, 0, 0, 0.6);" // полупрозрачный фон - "border-radius: 4px;" // скруглённые углы, если нужно - ); + auto *appsBoxLayout = new QVBoxLayout(appsView); + appsBoxLayout->setContentsMargins(2, 0, 0, 2); + appsBoxLayout->setSpacing(2); - // вешаем на контейнер ваш QHBoxLayout - auto *hbox = new QHBoxLayout(rowWidget); - hbox->setContentsMargins(2, 1, 1, 1); - hbox->setSpacing(0); - - auto *icon = new QLabel(overlay); - 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); + QList apps = ApplicationDAO::getApplicationsByDeviceId(device.id); + for (ApplicationInfo &app: apps) { + if (app.install) { + auto *rowWidget = new QWidget(appsView); + buildBankRow(app, rowWidget, appsView); + appsBoxLayout->addWidget(rowWidget, 1); + } } - vbox->addStretch(); - + // appsBoxLayout->addStretch(); // кладём 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); addBankBtn->setMaximumWidth(300); @@ -141,22 +207,6 @@ DeviceWidget::DeviceWidget(const DeviceInfo &device, QWidget *parent): QWidget(p "color: #000;" ); - QString deviceId = device.id; - QString deviceName = device.name; - connect(addBankBtn, &QPushButton::clicked, this, [deviceId, deviceName]() { - static QPointer 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(); - }); + setOpenBanksSettings(addBankBtn); layout->addWidget(addBankBtn, 1, 0); } diff --git a/views/device/DeviceWidget.h b/views/device/DeviceWidget.h index e37fc03..bbcc159 100644 --- a/views/device/DeviceWidget.h +++ b/views/device/DeviceWidget.h @@ -1,6 +1,9 @@ #pragma once +#include #include #include + +#include "db/ApplicationInfo.h" #include "db/DeviceInfo.h" class DeviceWidget final : public QWidget { @@ -8,4 +11,11 @@ class DeviceWidget final : public QWidget { public: 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); }; diff --git a/views/device/EditBankDialog.h b/views/device/EditBankDialog.h index e19e641..31013fb 100644 --- a/views/device/EditBankDialog.h +++ b/views/device/EditBankDialog.h @@ -4,45 +4,46 @@ #include #include #include +#include +#include "db/ApplicationInfo.h" -// 1) Определяем диалог -class BankEditDialog : public QDialog { +class BankEditDialog final : public QDialog { Q_OBJECT 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); firstEdit = new QLineEdit(this); - secondEdit = new QLineEdit(this); - toggle = new QCheckBox(tr("Включено"), this); + firstEdit->setText(app.pinCode); + 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("Второе поле:"), secondEdit); + form->addRow(tr("Пин-код:"), firstEdit); + form->addRow(tr("Комментарий:"), secondEdit); form->addRow(QString(), toggle); - // 3) Кнопки OK / Cancel - auto *buttons = new QDialogButtonBox( - QDialogButtonBox::Ok | QDialogButtonBox::Cancel, - Qt::Horizontal, - this - ); + auto *buttons = new QDialogButtonBox(Qt::Horizontal, this); + buttons->addButton(tr("Сохранить"), QDialogButtonBox::AcceptRole); + buttons->addButton(tr("Отмена"), QDialogButtonBox::RejectRole); form->addRow(buttons); connect(buttons, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); } + QString firstValue() const { return firstEdit->text(); } - QString secondValue() const { return secondEdit->text(); } + QString secondValue() const { return secondEdit->toPlainText(); } bool toggled() const { return toggle->isChecked(); } private: QLineEdit *firstEdit; - QLineEdit *secondEdit; + QTextEdit *secondEdit; QCheckBox *toggle; };