Refactor account handling and add TutorialWindow feature
Removed redundant "numbers" field from Account model and database queries to simplify account structure. Introduced a TutorialWindow class for displaying application tutorials, integrated into the main window, and updated file paths accordingly. Improved string handling and logic in Payment routines and enhanced overall UI structure.
This commit is contained in:
parent
5cb32ec4f1
commit
27ce69d094
@ -75,7 +75,7 @@ if (WIN32)
|
||||
|
||||
# Копирование изображений в директорию сборки
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/database/app_data.db" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/images" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/tutorials" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/rus.traineddata" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||
file(COPY "${CMAKE_CURRENT_SOURCE_DIR}/config.ini" DESTINATION "${CMAKE_BINARY_DIR}")
|
||||
|
||||
|
||||
@ -81,8 +81,7 @@ void PayByPhoneScript::makePayment(
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
QString payBankName;
|
||||
payBankName = m_bankName == "Россельхозбанк" ? "Россельхозбанк" : "Другой банк через СБП";
|
||||
QString payBankName = m_bankName == QStringLiteral("Россельхозбанк") ? "Россельхозбанк" : "Другой банк через СБП";
|
||||
Node payOtherBankBtn = xmlScreenParser.findButtonNode(payBankName, false);
|
||||
if (!payOtherBankBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, payOtherBankBtn.x(), payOtherBankBtn.y());
|
||||
@ -93,6 +92,7 @@ void PayByPhoneScript::makePayment(
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_bankName != QStringLiteral("Россельхозбанк")) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
for (int i = 0; i < 5; ++i) {
|
||||
Node allBanksTitle = xmlScreenParser.findTextNode("Банки участники");
|
||||
@ -125,16 +125,23 @@ void PayByPhoneScript::makePayment(
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
}
|
||||
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
|
||||
Node payByPhoneScreenTitle = xmlScreenParser.findTextNode("Перевод по телефону");
|
||||
const bool isRshbBank = m_bankName == QStringLiteral("Россельхозбанк");
|
||||
QString payByPhoneScreenTitleText = isRshbBank ? "Перевод клиенту РСХБ" : "Перевод по телефону";
|
||||
Node payByPhoneScreenTitle = xmlScreenParser.findTextNode(payByPhoneScreenTitleText);
|
||||
Node continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false);
|
||||
if (!payByPhoneScreenTitle.isEmpty() && payByPhoneScreenTitle.y() < 300 && !continuePayBtn.isEmpty()) {
|
||||
|
||||
if (isRshbBank ||
|
||||
!payByPhoneScreenTitle.isEmpty() && payByPhoneScreenTitle.y() < 300 && !continuePayBtn.isEmpty()) {
|
||||
Node sumBtn = xmlScreenParser.findButtonNode("0", false);
|
||||
if (!sumBtn.isEmpty()) {
|
||||
if (isRshbBank || !sumBtn.isEmpty()) {
|
||||
if (!isRshbBank) {
|
||||
AdbUtils::makeTap(deviceId, sumBtn.x(), sumBtn.y());
|
||||
QThread::msleep(500);
|
||||
}
|
||||
AdbUtils::inputText(deviceId, QString::number(m_amount));
|
||||
QThread::msleep(500);
|
||||
|
||||
@ -143,6 +150,11 @@ void PayByPhoneScript::makePayment(
|
||||
if (!readyBtn.isEmpty()) {
|
||||
AdbUtils::makeTap(deviceId, readyBtn.x(), readyBtn.y());
|
||||
QThread::msleep(500);
|
||||
if (isRshbBank) {
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
continuePayBtn = xmlScreenParser.findButtonNode("Продолжить", false);
|
||||
}
|
||||
AdbUtils::makeTap(deviceId, continuePayBtn.x(), continuePayBtn.y());
|
||||
QThread::msleep(1000);
|
||||
} else {
|
||||
@ -156,7 +168,7 @@ void PayByPhoneScript::makePayment(
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
m_error = "Не смогли найти 'Перевод по телефону'";
|
||||
m_error = "Не смогли найти заголовок: " + payByPhoneScreenTitleText;
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
}
|
||||
@ -187,7 +199,7 @@ void PayByPhoneScript::makePayment(
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
|
||||
if (!inputedPinCode) {
|
||||
if (!isRshbBank && !inputedPinCode) {
|
||||
m_error = "Не смогли ввести пин-код!'";
|
||||
qDebug() << m_error;
|
||||
return;
|
||||
@ -204,6 +216,9 @@ void PayByPhoneScript::makePayment(
|
||||
|
||||
// TODO считываем все данные по переводу
|
||||
TransactionInfo transaction = xmlScreenParser.parseTransactionInfo(width, height);
|
||||
if (isRshbBank) {
|
||||
transaction.bankName = m_bankName;
|
||||
}
|
||||
transaction.accountId = m_account.id;
|
||||
transaction.amount = -m_amount;
|
||||
transaction.type = TransactionType::Phone;
|
||||
@ -217,6 +232,7 @@ void PayByPhoneScript::makePayment(
|
||||
for (int k = 0; k < 10; ++k) {
|
||||
Node paymentCompleted = xmlScreenParser.findTextNode("Исполнен", 0, 0, width, height / 2);
|
||||
if (!paymentCompleted.isEmpty()) {
|
||||
transaction.status = TransactionStatus::Complete;
|
||||
if (!TransactionDAO::updateTransactionStatus(transaction.id, TransactionStatus::Complete)) {
|
||||
qDebug() << "Not updated: " << transaction.id;
|
||||
}
|
||||
@ -227,6 +243,8 @@ void PayByPhoneScript::makePayment(
|
||||
QThread::msleep(1000);
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(deviceId, ++m_counter));
|
||||
}
|
||||
|
||||
postNewTransactionData(m_account, transaction);
|
||||
break;
|
||||
} else {
|
||||
qWarning() << "--- moreDetailBtn not found, i: " << i;
|
||||
@ -245,11 +263,22 @@ void PayByPhoneScript::doStart() {
|
||||
const ApplicationInfo app = ApplicationDAO::getApplicationsByDeviceId(m_account.deviceId, m_account.appName);
|
||||
|
||||
if (device.id.isEmpty() || app.id.isEmpty()) {
|
||||
qDebug() << "Device or app not found....";
|
||||
m_error = "Device or app not found....";
|
||||
qDebug() << m_error;
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, device.screenWidth, device.screenHeight)) {
|
||||
xmlScreenParser.parseAndSaveXml(AdbUtils::getScreenDumpAsXml(m_account.deviceId, ++m_counter));
|
||||
if (!xmlScreenParser.isHomeScreen()) {
|
||||
if (!runAppAndGoToHomeScreen(device.id, app.package, app.pinCode, device.screenWidth, device.screenHeight)) {
|
||||
m_error = "Не смогли дойти до домашней страницы....";
|
||||
qDebug() << m_error;
|
||||
emit finishedWithResult(m_error);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (goToAllProducts(device.id, device.screenWidth, device.screenHeight)) {
|
||||
qDebug() << "======== 1. All products";
|
||||
QList<QPair<AccountInfo, Node> > accounts = xmlScreenParser.parseAccountsInfo();
|
||||
@ -260,6 +289,9 @@ void PayByPhoneScript::doStart() {
|
||||
qDebug() << "Not updated: " << account.lastNumbers;
|
||||
}
|
||||
}
|
||||
|
||||
postAccountsData(accounts);
|
||||
|
||||
for (auto &[account, node]: accounts) {
|
||||
// FIXME будем считать что числа последние везде уникальные
|
||||
if (m_account.lastNumbers == account.lastNumbers) {
|
||||
@ -276,10 +308,6 @@ void PayByPhoneScript::doStart() {
|
||||
m_error = "Не смогли открыть 'Все продукты'";
|
||||
qDebug() << m_error;
|
||||
}
|
||||
} else {
|
||||
m_error = "Не смогли дойти до домашней страницы....";
|
||||
qDebug() << m_error;
|
||||
}
|
||||
|
||||
emit finishedWithResult(m_error);
|
||||
}
|
||||
|
||||
15
android/rshb/SyncAccountDataScript.cpp
Normal file
15
android/rshb/SyncAccountDataScript.cpp
Normal file
@ -0,0 +1,15 @@
|
||||
#include "SyncAccountDataScript.h"
|
||||
|
||||
SyncAccountDataScript::SyncAccountDataScript(
|
||||
QString deviceId,
|
||||
QObject *parent
|
||||
) : CommonScript(parent),
|
||||
m_deviceId(std::move(deviceId)) {
|
||||
}
|
||||
|
||||
SyncAccountDataScript::~SyncAccountDataScript() = default;
|
||||
|
||||
void SyncAccountDataScript::doStart() {
|
||||
|
||||
// 1.
|
||||
}
|
||||
21
android/rshb/SyncAccountDataScript.h
Normal file
21
android/rshb/SyncAccountDataScript.h
Normal file
@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "CommonScript.h"
|
||||
|
||||
class SyncAccountDataScript final : public CommonScript {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
~SyncAccountDataScript() override;
|
||||
|
||||
explicit SyncAccountDataScript(
|
||||
QString deviceId,
|
||||
QObject *parent = nullptr
|
||||
);
|
||||
|
||||
protected:
|
||||
void doStart() override;
|
||||
|
||||
private:
|
||||
const QString m_deviceId;
|
||||
};
|
||||
|
||||
Binary file not shown.
@ -10,8 +10,8 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
INSERT INTO accounts (device_id, app_name, numbers, last_numbers, amount, update_time, status, description)
|
||||
VALUES (:device_id, :app_name, :numbers, :last_numbers, :amount, :update_time, :status, :description)
|
||||
INSERT INTO accounts (device_id, app_name, last_numbers, amount, update_time, status, description)
|
||||
VALUES (:device_id, :app_name, :last_numbers, :amount, :update_time, :status, :description)
|
||||
ON CONFLICT(device_id, app_name, last_numbers)
|
||||
DO UPDATE SET
|
||||
amount = excluded.amount,
|
||||
@ -22,7 +22,6 @@ bool AccountDAO::upsertAccount(const AccountInfo &info) {
|
||||
|
||||
query.bindValue(":device_id", info.deviceId);
|
||||
query.bindValue(":app_name", info.appName);
|
||||
query.bindValue(":numbers", info.numbers);
|
||||
query.bindValue(":last_numbers", info.lastNumbers);
|
||||
query.bindValue(":amount", info.amount);
|
||||
query.bindValue(":update_time", DateUtils::toUtcString(info.updateTime));
|
||||
@ -50,7 +49,6 @@ AccountInfo parseAccount(const QSqlQuery &query) {
|
||||
acc.deviceId = query.value("device_id").toString();
|
||||
acc.appName = query.value("app_name").toString();
|
||||
acc.lastNumbers = query.value("last_numbers").toString();
|
||||
acc.numbers = query.value("numbers").toString();
|
||||
acc.amount = query.value("amount").toDouble();
|
||||
acc.updateTime = DateUtils::fromUtcString(query.value("update_time").toString());
|
||||
acc.status = accountStatusFromString(query.value("status").toString());
|
||||
@ -63,7 +61,7 @@ QList<AccountInfo> AccountDAO::getAccounts(const QString &deviceId, const QStrin
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||
SELECT id, device_id, app_name, last_numbers, amount, update_time, status, description
|
||||
FROM accounts
|
||||
WHERE device_id = :device_id AND app_name = :app_name
|
||||
)");
|
||||
@ -87,7 +85,7 @@ AccountInfo AccountDAO::getAccountById(const int accountId) {
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||
SELECT id, device_id, app_name, last_numbers, amount, update_time, status, description
|
||||
FROM accounts
|
||||
WHERE id = :id
|
||||
LIMIT 1
|
||||
@ -110,7 +108,7 @@ AccountInfo AccountDAO::findAppAccount(const QString &deviceId, const QString &a
|
||||
QSqlQuery query(DatabaseManager::instance().database());
|
||||
|
||||
query.prepare(R"(
|
||||
SELECT id, device_id, app_name, numbers, last_numbers, amount, update_time, status, description
|
||||
SELECT id, device_id, app_name, last_numbers, amount, update_time, status, description
|
||||
FROM accounts
|
||||
WHERE app_name = :appName AND last_numbers = :lastNumber AND device_id = :deviceId
|
||||
LIMIT 1
|
||||
|
||||
@ -40,7 +40,7 @@ int EventDAO::insertEvent(const EventInfo &event) {
|
||||
query.bindValue(":amount", event.amount);
|
||||
query.bindValue(":phone", event.phone);
|
||||
query.bindValue(":bank_name", event.bankName);
|
||||
query.bindValue(":timestamp", DateUtils::toUtcString(DateUtils::getUtcNow()));
|
||||
query.bindValue(":timestamp", event.timestamp);
|
||||
|
||||
if (!query.exec()) {
|
||||
qCritical() << "Ошибка при вставке event:" << query.lastError().text();
|
||||
|
||||
61
main.cpp
61
main.cpp
@ -11,7 +11,7 @@
|
||||
#include "automation_script/Script.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdlib>
|
||||
#include <QStyleFactory>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <QFile>
|
||||
@ -191,35 +191,38 @@ void messageHandler(
|
||||
int main(int argc, char *argv[]) {
|
||||
QApplication app(argc, argv);
|
||||
|
||||
logFile.setFileName("application.log");
|
||||
if (!logFile.open(QIODevice::Append | QIODevice::Text)) {
|
||||
qWarning() << "Не удалось открыть лог-файл для записи";
|
||||
}
|
||||
// Устанавливаем свой обработчик
|
||||
qInstallMessageHandler(messageHandler);
|
||||
// Выбрать стиль Fusion
|
||||
app.setStyle(QStyleFactory::create("Fusion"));
|
||||
|
||||
auto *thread = new QThread;
|
||||
auto *worker = new EventHandler;
|
||||
worker->moveToThread(thread);
|
||||
QObject::connect(thread, &QThread::started, worker, &EventHandler::start);
|
||||
QObject::connect(worker, &EventHandler::finished, thread, &QThread::quit);
|
||||
QObject::connect(worker, &EventHandler::finished, worker, &QObject::deleteLater);
|
||||
QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
thread->start();
|
||||
|
||||
auto *paymentThread = new QThread;
|
||||
auto *networkService = new NetworkService;
|
||||
|
||||
// FIXME доставить десктоп ид из настроеек
|
||||
networkService->setDeviceData("DT88bokcwQ", "");
|
||||
networkService->moveToThread(paymentThread);
|
||||
QObject::connect(paymentThread, &QThread::started, networkService, &NetworkService::start);
|
||||
QObject::connect(networkService, &NetworkService::finished, paymentThread, &QThread::quit);
|
||||
QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
|
||||
QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
|
||||
paymentThread->start();
|
||||
|
||||
setupWorkerAndThread(app);
|
||||
// logFile.setFileName("application.log");
|
||||
// if (!logFile.open(QIODevice::Append | QIODevice::Text)) {
|
||||
// qWarning() << "Не удалось открыть лог-файл для записи";
|
||||
// }
|
||||
// // Устанавливаем свой обработчик
|
||||
// qInstallMessageHandler(messageHandler);
|
||||
//
|
||||
// auto *thread = new QThread;
|
||||
// auto *worker = new EventHandler;
|
||||
// worker->moveToThread(thread);
|
||||
// QObject::connect(thread, &QThread::started, worker, &EventHandler::start);
|
||||
// QObject::connect(worker, &EventHandler::finished, thread, &QThread::quit);
|
||||
// QObject::connect(worker, &EventHandler::finished, worker, &QObject::deleteLater);
|
||||
// QObject::connect(thread, &QThread::finished, thread, &QThread::deleteLater);
|
||||
// thread->start();
|
||||
//
|
||||
// auto *paymentThread = new QThread;
|
||||
// auto *networkService = new NetworkService;
|
||||
//
|
||||
// // FIXME доставить десктоп ид из настроеек
|
||||
// networkService->setDeviceData("DT88bokcwQ", "");
|
||||
// networkService->moveToThread(paymentThread);
|
||||
// QObject::connect(paymentThread, &QThread::started, networkService, &NetworkService::start);
|
||||
// QObject::connect(networkService, &NetworkService::finished, paymentThread, &QThread::quit);
|
||||
// QObject::connect(networkService, &NetworkService::finished, networkService, &QObject::deleteLater);
|
||||
// QObject::connect(paymentThread, &QThread::finished, paymentThread, &QThread::deleteLater);
|
||||
// paymentThread->start();
|
||||
//
|
||||
// setupWorkerAndThread(app);
|
||||
|
||||
MainWindow window;
|
||||
window.show();
|
||||
|
||||
@ -31,7 +31,6 @@ CREATE TABLE IF NOT EXISTS accounts
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
device_id TEXT,
|
||||
app_name TEXT,
|
||||
numbers TEXT,
|
||||
last_numbers TEXT,
|
||||
description TEXT,
|
||||
amount REAL,
|
||||
|
||||
@ -26,7 +26,6 @@ struct AccountInfo {
|
||||
QString deviceId;
|
||||
QString appName;
|
||||
QString lastNumbers;
|
||||
QString numbers;
|
||||
double amount = 0.0;
|
||||
QDateTime updateTime;
|
||||
AccountStatus status = AccountStatus::Active;
|
||||
@ -42,7 +41,6 @@ inline QString convertAccountToString(const AccountInfo &info) {
|
||||
if (info.id != -1) lines << " id: " + QString::number(info.id); else emptyFields << "id";
|
||||
if (!info.deviceId.isEmpty()) lines << " deviceId: " + info.deviceId; else emptyFields << "deviceId";
|
||||
if (!info.appName.isEmpty()) lines << " appName: " + info.appName; else emptyFields << "appName";
|
||||
if (!info.numbers.isEmpty()) lines << " numbers: " + info.numbers; else emptyFields << "numbers";
|
||||
if (!info.lastNumbers.isEmpty()) lines << " lastNumbers: " + info.lastNumbers; else emptyFields << "lastNumbers";
|
||||
if (info.amount != 0.0) lines << " amount: " + QString::number(info.amount, 'f', 2); else emptyFields << "amount";
|
||||
if (info.updateTime.isValid()) lines << " updateTime: " + info.updateTime.toString(Qt::ISODate); else emptyFields << "updateTime";
|
||||
|
||||
@ -8,6 +8,7 @@
|
||||
#include <QThread>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include "dao/AccountDAO.h"
|
||||
#include "dao/EventDAO.h"
|
||||
@ -144,6 +145,7 @@ void NetworkService::getPayments() {
|
||||
EventInfo eventInfo;
|
||||
eventInfo.status = EventStatus::Wait;
|
||||
eventInfo.externalId = obj["id"].toInt();
|
||||
eventInfo.timestamp = QDateTime::fromMSecsSinceEpoch(obj["timestamp"].toDouble(), QTimeZone::utc());
|
||||
eventInfo.amount = obj["amount"].toDouble();
|
||||
eventInfo.bankName = obj["bankName"].toString();
|
||||
eventInfo.phone = obj["phone"].toString();
|
||||
|
||||
17
tutorials/test.md
Normal file
17
tutorials/test.md
Normal file
@ -0,0 +1,17 @@
|
||||
# Руководство: Запуск приложения TutorialWindow
|
||||
|
||||
Этот файл объясняет, как собрать и запустить ваше Qt6-приложение с окном TutorialWindow.
|
||||
|
||||
---
|
||||
|
||||
## 1. Требования
|
||||
|
||||
- Qt 6 (рекомендуется версия 6.2 или выше)
|
||||
- C++17-совместимый компилятор (GCC, Clang, MSVC)
|
||||
- CMake (версия ≥ 3.16) или qmake
|
||||
|
||||
---
|
||||
|
||||
## 2. Структура проекта
|
||||
[Текст ссылки](https://example.com)
|
||||
|
||||
16
tutorials/test2.md
Normal file
16
tutorials/test2.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Руководство: Запуск приложения TutorialWindow
|
||||
|
||||
Этот файл объясняет, как собрать и запустить ваше Qt6-приложение с окном TutorialWindow.
|
||||
|
||||
---
|
||||
|
||||
## 1. Требования
|
||||
|
||||
- Qt 6 (рекомендуется версия 6.2 или выше)
|
||||
- C++17-совместимый компилятор (GCC, Clang, MSVC)
|
||||
- CMake (версия ≥ 3.16) или qmake
|
||||
|
||||
---
|
||||
|
||||
## 2. Структура проекта
|
||||
|
||||
16
tutorials/test3.md
Normal file
16
tutorials/test3.md
Normal file
@ -0,0 +1,16 @@
|
||||
# Руководство: Запуск приложения TutorialWindow
|
||||
|
||||
Этот файл объясняет, как собрать и запустить ваше Qt6-приложение с окном TutorialWindow.
|
||||
|
||||
---
|
||||
|
||||
## 1. Требования
|
||||
|
||||
- Qt 6 (рекомендуется версия 6.2 или выше)
|
||||
- C++17-совместимый компилятор (GCC, Clang, MSVC)
|
||||
- CMake (версия ≥ 3.16) или qmake
|
||||
|
||||
---
|
||||
|
||||
## 2. Структура проекта
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
#include "MainWindow.h"
|
||||
#include "AccountWindow.h"
|
||||
#include "bank/AccountWindow.h"
|
||||
|
||||
#include <QLabel>
|
||||
#include <QScrollArea>
|
||||
#include <QMenuBar>
|
||||
#include <QStackedWidget>
|
||||
|
||||
#include "TutorialWindow.h"
|
||||
#include "db/DeviceInfo.h"
|
||||
#include "DeviceWidget.h"
|
||||
#include "device/DeviceWidget.h"
|
||||
#include "dao/DeviceDAO.h"
|
||||
#include "widget/FlowLayout.h"
|
||||
#include "widget/common/FlowLayout.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
// Инициализируем таймер обновления
|
||||
@ -20,12 +21,12 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
setCentralWidget(stackedWidget);
|
||||
|
||||
createDevicePage();
|
||||
stackedWidget->addWidget(devicePage);
|
||||
stackedWidget->setCurrentWidget(devicePage);
|
||||
stackedWidget->addWidget(m_devicesWindow);
|
||||
stackedWidget->setCurrentWidget(m_devicesWindow);
|
||||
|
||||
// FIXME переделать на ленивое создание
|
||||
accountPage = new AccountWindow(this);
|
||||
stackedWidget->addWidget(accountPage);
|
||||
m_accountsWindow = new AccountWindow(this);
|
||||
stackedWidget->addWidget(m_accountsWindow);
|
||||
// stackedWidget->setCurrentWidget(accountPage);
|
||||
|
||||
resize(800, 600);
|
||||
@ -34,25 +35,43 @@ MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
|
||||
QMenu *menu = menuBar()->addMenu("Меню");
|
||||
auto *devicesPageAction = new QAction("Активные устройства", this);
|
||||
auto *accountPageAction = new QAction("Аккаунты", this);
|
||||
auto *tutorialPageAction = new QAction("Инструкция", this);
|
||||
menu->addAction(devicesPageAction);
|
||||
menu->addAction(accountPageAction);
|
||||
menu->addAction(tutorialPageAction);
|
||||
|
||||
connect(devicesPageAction, &QAction::triggered, this, [=]() {
|
||||
if (!devicePage) {
|
||||
if (!m_devicesWindow) {
|
||||
createDevicePage();
|
||||
}
|
||||
stackedWidget->setCurrentWidget(devicePage);
|
||||
stackedWidget->setCurrentWidget(m_devicesWindow);
|
||||
});
|
||||
|
||||
connect(accountPageAction, &QAction::triggered, this, [=]() {
|
||||
stackedWidget->setCurrentWidget(accountPage);
|
||||
stackedWidget->setCurrentWidget(m_accountsWindow);
|
||||
deleteDevicePage(); // освобождаем ресурсы
|
||||
});
|
||||
|
||||
connect(tutorialPageAction, &QAction::triggered, this, [=]() {
|
||||
if (!m_tutorialWindow) {
|
||||
m_tutorialWindow = new TutorialWindow(this);
|
||||
}
|
||||
m_tutorialWindow->show();
|
||||
m_tutorialWindow->raise();
|
||||
m_tutorialWindow->activateWindow();
|
||||
});
|
||||
|
||||
m_tutorialWindow = new TutorialWindow(this);
|
||||
m_tutorialWindow->show();
|
||||
QTimer::singleShot(0, m_tutorialWindow, [w = m_tutorialWindow]() {
|
||||
w->raise();
|
||||
w->activateWindow();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
void MainWindow::createDevicePage() {
|
||||
if (devicePage) {
|
||||
if (m_devicesWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -73,22 +92,22 @@ void MainWindow::createDevicePage() {
|
||||
scrollArea->setWidget(contentWidget);
|
||||
scrollArea->setWidgetResizable(true);
|
||||
|
||||
devicePage = scrollArea;
|
||||
stackedWidget->addWidget(devicePage);
|
||||
m_devicesWindow = scrollArea;
|
||||
stackedWidget->addWidget(m_devicesWindow);
|
||||
|
||||
timer->start(1000);
|
||||
}
|
||||
|
||||
void MainWindow::deleteDevicePage() {
|
||||
if (!devicePage) {
|
||||
if (!m_devicesWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
timer->stop();
|
||||
|
||||
stackedWidget->removeWidget(devicePage);
|
||||
devicePage->deleteLater();
|
||||
devicePage = nullptr;
|
||||
stackedWidget->removeWidget(m_devicesWindow);
|
||||
m_devicesWindow->deleteLater();
|
||||
m_devicesWindow = nullptr;
|
||||
flowLayout = nullptr;
|
||||
}
|
||||
|
||||
|
||||
@ -3,24 +3,31 @@
|
||||
#include <QVBoxLayout>
|
||||
#include <QStackedWidget>
|
||||
#include <QTimer>
|
||||
|
||||
#include "TutorialWindow.h"
|
||||
#include "bank/AccountWindow.h"
|
||||
#include "db/DeviceInfo.h"
|
||||
#include "widget/FlowLayout.h"
|
||||
#include "widget/common/FlowLayout.h"
|
||||
|
||||
class MainWindow final : public QMainWindow {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit MainWindow(QWidget *parent = nullptr);
|
||||
|
||||
void loadDevices();
|
||||
|
||||
private:
|
||||
QWidget* central;
|
||||
FlowLayout* flowLayout;
|
||||
QTimer* timer;
|
||||
QStackedWidget* stackedWidget;
|
||||
QWidget *central;
|
||||
FlowLayout *flowLayout;
|
||||
QTimer *timer;
|
||||
QStackedWidget *stackedWidget;
|
||||
|
||||
QWidget* devicePage = nullptr;
|
||||
QWidget* accountPage = nullptr;
|
||||
QWidget *m_devicesWindow = nullptr;
|
||||
QWidget *m_accountsWindow = nullptr;
|
||||
QWidget *m_tutorialWindow = nullptr;
|
||||
|
||||
void createDevicePage();
|
||||
|
||||
void deleteDevicePage();
|
||||
};
|
||||
|
||||
63
views/TutorialWindow.cpp
Normal file
63
views/TutorialWindow.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "TutorialWindow.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QTextStream>
|
||||
#include <QScrollArea>
|
||||
#include <QCoreApplication>
|
||||
#include "widget/common/CollapsibleSection.h"
|
||||
|
||||
TutorialWindow::TutorialWindow(QWidget *parent)
|
||||
: QWidget(parent,
|
||||
Qt::Window
|
||||
| Qt::WindowTitleHint
|
||||
| Qt::WindowSystemMenuHint
|
||||
| Qt::WindowMinMaxButtonsHint) {
|
||||
setWindowTitle(tr("Инструкция"));
|
||||
setMinimumSize(800, 400);
|
||||
resize(1000, 600);
|
||||
|
||||
// Основной контейнер со всеми разделами
|
||||
auto *container = new QWidget(this);
|
||||
auto *layout = new QVBoxLayout(container);
|
||||
layout->setSpacing(10);
|
||||
|
||||
const QString tutorialsPath = QCoreApplication::applicationDirPath() + "/tutorials";
|
||||
// Загружаем Markdown-файлы
|
||||
const QDir dir(tutorialsPath);
|
||||
const QStringList files = dir.entryList({"*.md"}, QDir::Files, QDir::Name);
|
||||
for (int i = 0; i < files.size(); ++i) {
|
||||
const QString &fileName = files.at(i);
|
||||
|
||||
QFile file(dir.absoluteFilePath(fileName));
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
|
||||
continue;
|
||||
QTextStream in(&file);
|
||||
QString titleLine = in.readLine().trimmed();
|
||||
QString content = in.readAll().trimmed();
|
||||
file.close();
|
||||
|
||||
QString title = !titleLine.isEmpty() ? titleLine.replace("#", "") : QFileInfo(file).baseName();
|
||||
|
||||
auto *section = new CollapsibleSection(title, content, container, i == 0);
|
||||
layout->addWidget(section);
|
||||
|
||||
if (i == 0) {
|
||||
}
|
||||
}
|
||||
layout->addStretch();
|
||||
|
||||
QSpacerItem *spacer = new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding);
|
||||
layout->addItem(spacer);
|
||||
|
||||
// Скролл вокруг контейнера
|
||||
auto *scroll = new QScrollArea(this);
|
||||
scroll->setWidget(container);
|
||||
scroll->setWidgetResizable(true);
|
||||
scroll->setAlignment(Qt::AlignTop | Qt::AlignLeft);
|
||||
|
||||
auto *mainLayout = new QVBoxLayout(this);
|
||||
mainLayout->setContentsMargins(0, 0, 0, 0);
|
||||
mainLayout->addWidget(scroll);
|
||||
setLayout(mainLayout);
|
||||
}
|
||||
9
views/TutorialWindow.h
Normal file
9
views/TutorialWindow.h
Normal file
@ -0,0 +1,9 @@
|
||||
#pragma once
|
||||
#include <QWidget>
|
||||
|
||||
class TutorialWindow final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit TutorialWindow(QWidget *parent = nullptr);
|
||||
};
|
||||
85
views/widget/common/CollapsibleSection.cpp
Normal file
85
views/widget/common/CollapsibleSection.cpp
Normal file
@ -0,0 +1,85 @@
|
||||
#include "CollapsibleSection.h"
|
||||
#include <QVBoxLayout>
|
||||
#include <QTextBrowser>
|
||||
#include <QEvent>
|
||||
#include <QDesktopServices>
|
||||
|
||||
CollapsibleSection::CollapsibleSection(
|
||||
const QString &title,
|
||||
const QString &markdownContent,
|
||||
QWidget *parent,
|
||||
const bool expanded
|
||||
): QWidget(parent) {
|
||||
// Кнопка заголовка с стрелкой
|
||||
m_toggleButton = new QToolButton(this);
|
||||
m_toggleButton->setText(title);
|
||||
m_toggleButton->setCheckable(true);
|
||||
m_toggleButton->setChecked(expanded);
|
||||
m_toggleButton->setToolButtonStyle(Qt::ToolButtonTextBesideIcon);
|
||||
m_toggleButton->setArrowType(expanded ? Qt::DownArrow : Qt::RightArrow);
|
||||
|
||||
connect(m_toggleButton, &QToolButton::toggled, this, &CollapsibleSection::onToggled);
|
||||
|
||||
// Контент без собственного скрола, займёт доступное пространство
|
||||
m_contentBrowser = new QTextBrowser(this);
|
||||
m_contentBrowser->setMarkdown(markdownContent);
|
||||
m_contentBrowser->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_contentBrowser->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
m_contentBrowser->setFrameShape(QFrame::NoFrame);
|
||||
m_contentBrowser->setVisible(expanded);
|
||||
m_contentBrowser->document()->adjustSize();
|
||||
// после создания textBrowser:
|
||||
m_contentBrowser->setReadOnly(true);
|
||||
|
||||
// 1) Отключаем встроенные переходы
|
||||
m_contentBrowser->setOpenLinks(false);
|
||||
|
||||
// 2) Подключаемся к клику по ссылке
|
||||
connect(m_contentBrowser, &QTextBrowser::anchorClicked,
|
||||
this, [](const QUrl &url) {
|
||||
QDesktopServices::openUrl(url);
|
||||
});
|
||||
m_contentBrowser->setStyleSheet(
|
||||
"QTextBrowser {"
|
||||
" border: 1px solid #ccc;" // рамка (по желанию)
|
||||
" border-radius: 3px;" // радиус скругления
|
||||
" background-color: white;" // фон (по желанию)
|
||||
"}"
|
||||
);
|
||||
updateContentHeight();
|
||||
|
||||
m_contentBrowser->viewport()->installEventFilter(this);
|
||||
|
||||
// Компоновка
|
||||
auto *layout = new QVBoxLayout(this);
|
||||
layout->setSpacing(0);
|
||||
layout->setContentsMargins(0, 0, 0, 0);
|
||||
layout->addWidget(m_toggleButton);
|
||||
layout->addSpacing(5);
|
||||
layout->addWidget(m_contentBrowser);
|
||||
}
|
||||
|
||||
// Перестраховка: запрещаем прокрутку колесом
|
||||
bool CollapsibleSection::eventFilter(QObject *watched, QEvent *event) {
|
||||
if (watched == m_contentBrowser->viewport() && event->type() == QEvent::Resize) {
|
||||
updateContentHeight();
|
||||
}
|
||||
return QWidget::eventFilter(watched, event);
|
||||
}
|
||||
|
||||
void CollapsibleSection::updateContentHeight() const {
|
||||
const qreal w = m_contentBrowser->viewport()->width();
|
||||
m_contentBrowser->document()->setTextWidth(w);
|
||||
const qreal h = m_contentBrowser->document()->size().height();
|
||||
// Можно добавить +1 пиксел на погрешность:
|
||||
m_contentBrowser->setFixedHeight(static_cast<int>(h) + 5);
|
||||
}
|
||||
|
||||
void CollapsibleSection::onToggled(const bool checked) const {
|
||||
m_toggleButton->setArrowType(checked ? Qt::DownArrow : Qt::RightArrow);
|
||||
m_contentBrowser->setVisible(checked);
|
||||
|
||||
if (checked) {
|
||||
updateContentHeight();
|
||||
}
|
||||
}
|
||||
28
views/widget/common/CollapsibleSection.h
Normal file
28
views/widget/common/CollapsibleSection.h
Normal file
@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include <QWidget>
|
||||
#include <QToolButton>
|
||||
#include <QTextBrowser>
|
||||
|
||||
class CollapsibleSection final : public QWidget {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
explicit CollapsibleSection(
|
||||
const QString &title,
|
||||
const QString &markdownContent,
|
||||
QWidget *parent = nullptr,
|
||||
bool expanded = false
|
||||
);
|
||||
|
||||
private slots:
|
||||
void onToggled(bool checked) const;
|
||||
|
||||
private:
|
||||
QToolButton *m_toggleButton;
|
||||
QTextBrowser *m_contentBrowser;
|
||||
|
||||
void updateContentHeight() const;
|
||||
|
||||
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||
};
|
||||
Loading…
Reference in New Issue
Block a user