#include "invitedialog.h" #include "qrcode_utils.h" #include "../db/db_manager.h" #include "../transport/gui_bridge.h" #include "debug_ui.h" #include #include #include #include #include #include #include #include #include #include extern "C" { #include "../../../src/chat/invite_build.h" #include "../../../lib/mem.h" } ShareInviteDialog::ShareInviteDialog(const QString& channelId, DbManager* db, QWidget* parent) : QDialog(parent) , m_channelId(channelId) , m_db(db) { setWindowTitle(QString::fromUtf8("Пригласительная ссылка")); setMinimumSize(420, 560); setModal(true); auto *layout = new QVBoxLayout(this); layout->setSpacing(12); auto *nodeLabel = new QLabel(QString::fromUtf8("Узел входа:"), this); nodeLabel->setStyleSheet("font-size: 12px; color: palette(window-text);"); m_nodeCombo = new QComboBox(this); m_nodeCombo->setStyleSheet( "QComboBox { border: 1px solid palette(mid); border-radius: 4px;" " padding: 6px 10px; font-size: 13px; background: palette(base); }"); auto *nodeRow = new QHBoxLayout(); nodeRow->setSpacing(8); nodeRow->addWidget(nodeLabel); nodeRow->addWidget(m_nodeCombo, 1); layout->addLayout(nodeRow); m_linkEdit = new QLineEdit(this); m_linkEdit->setReadOnly(true); m_linkEdit->setPlaceholderText(QString::fromUtf8("Генерация ссылки...")); m_linkEdit->setStyleSheet("padding: 6px; font-family: monospace;"); auto *copyBtn = new QPushButton(QString::fromUtf8("Копировать"), this); connect(copyBtn, &QPushButton::clicked, this, [this]() { QClipboard* cb = QApplication::clipboard(); cb->setText(m_linkEdit->text(), QClipboard::Clipboard); if (cb->supportsSelection()) cb->setText(m_linkEdit->text(), QClipboard::Selection); m_linkEdit->setFocus(); }); auto *linkRow = new QHBoxLayout(); linkRow->setSpacing(8); linkRow->addWidget(m_linkEdit, 1); linkRow->addWidget(copyBtn); layout->addLayout(linkRow); m_qrLabel = new QLabel(this); m_qrLabel->setAlignment(Qt::AlignCenter); m_qrLabel->setMinimumSize(180, 180); layout->addWidget(m_qrLabel, 0, Qt::AlignCenter); auto *closeBtn = new QPushButton("OK", this); connect(closeBtn, &QPushButton::clicked, this, &QDialog::accept); layout->addWidget(closeBtn, 0, Qt::AlignRight); connect(m_nodeCombo, QOverload::of(&QComboBox::currentIndexChanged), this, &ShareInviteDialog::onNodeSelected); /* колбэки результатов (по образцу JoinDialog) */ gui_bridge_set_invite_link_ready_cb([](const char* chId, int chIdLen, const char* link) { ShareInviteDialog* dlg = qobject_cast(QApplication::activeModalWidget()); if (dlg) dlg->onLinkReady(chId, chIdLen, link); }); gui_bridge_set_invite_candidates_cb([](const char* chId, int chIdLen, const uint8_t* data, int len) { ShareInviteDialog* dlg = qobject_cast(QApplication::activeModalWidget()); if (dlg) dlg->onCandidates(chId, chIdLen, data, len); }); /* запросить кандидатов; линк строится после их прихода */ QByteArray chBytes = m_channelId.toUtf8(); gui_bridge_post_uasync_fn(chat_invite_candidates_trampoline, u_strdup(chBytes.constData())); } ShareInviteDialog::~ShareInviteDialog() { gui_bridge_set_invite_link_ready_cb(nullptr); gui_bridge_set_invite_candidates_cb(nullptr); } void ShareInviteDialog::onNodeSelected(int index) { if (index < 0) return; qulonglong nodeId = m_nodeCombo->itemData(index).toULongLong(); requestLink((uint64_t)nodeId); } void ShareInviteDialog::requestLink(uint64_t targetNodeId) { struct chat_invite_build_req* req = (struct chat_invite_build_req*)u_malloc(sizeof(*req)); if (!req) { GUI_ERROR("invite: requestLink — alloc failed"); return; } memset(req, 0, sizeof(*req)); QByteArray chBytes = m_channelId.toUtf8(); strncpy(req->ch_id, chBytes.constData(), sizeof(req->ch_id) - 1); req->target_node_id = targetNodeId; m_linkEdit->setText(QString()); m_linkEdit->setPlaceholderText(QString::fromUtf8("Генерация ссылки...")); m_qrLabel->clear(); gui_bridge_post_uasync_fn(chat_invite_build_link_trampoline, req); } void ShareInviteDialog::setLink(const QString& link) { m_linkEdit->setText(link); QImage qrImage = generateQRCode(link); if (!qrImage.isNull()) m_qrLabel->setPixmap(QPixmap::fromImage(qrImage).scaled( 180, 180, Qt::KeepAspectRatio, Qt::SmoothTransformation)); adjustSize(); } void ShareInviteDialog::onCandidates(const char* chId, int chIdLen, const uint8_t* data, int len) { QString cid = QString::fromUtf8(chId, chIdLen); if (cid != m_channelId) return; if (!data || len < 10) return; uint64_t autoId = 0; memcpy(&autoId, data, 8); uint16_t count = 0; memcpy(&count, data + 8, 2); if (autoId == 0) { m_linkEdit->setText(QString()); m_linkEdit->setPlaceholderText(QString::fromUtf8("Не удалось определить узел (см. лог)")); return; } auto nodeLabel = [this](uint64_t nid) { QString name = m_db ? m_db->getDisplayName(nid) : QString(); if (name.isEmpty()) name = QString("0x%1").arg(nid, 16, 16, QChar('0')); return name; }; /* блокируем сигналы, чтобы не перезапрашивать линк при добавлении пунктов */ QSignalBlocker blocker(m_nodeCombo); m_nodeCombo->clear(); /* пункт 0 — узел, найденный авто-выбором */ m_nodeCombo->addItem(nodeLabel(autoId) + QString::fromUtf8(" (auto)"), QVariant::fromValue((qulonglong)autoId)); /* остальные узлы (пропуская авто), свой помечаем (self) */ int off = 10; for (int i = 0; i < (int)count && off + 8 <= len; i++) { uint64_t nid = 0; memcpy(&nid, data + off, 8); off += 8; if (nid == autoId) continue; QString label = nodeLabel(nid); if (m_db && nid == m_db->myNodeId()) label += QString::fromUtf8(" (self)"); m_nodeCombo->addItem(label, QVariant::fromValue((qulonglong)nid)); } /* строим линк для авто-узла (сигнал заблокирован) */ requestLink(autoId); } void ShareInviteDialog::onLinkReady(const char* chId, int chIdLen, const char* link) { QString cid = QString::fromUtf8(chId, chIdLen); if (cid != m_channelId) return; if (!link || link[0] == '\0') { m_linkEdit->setText(QString()); m_linkEdit->setPlaceholderText(QString::fromUtf8("Не удалось собрать ссылку (см. лог)")); m_qrLabel->clear(); return; } setLink(QString::fromUtf8(link)); }