diff --git a/tools/chatgui/CMakeLists.txt b/tools/chatgui/CMakeLists.txt index 167fce8f..c4beeb9c 100644 --- a/tools/chatgui/CMakeLists.txt +++ b/tools/chatgui/CMakeLists.txt @@ -121,6 +121,7 @@ add_executable(vibechat src/qrcode_utils.cpp src/invite_link.cpp src/invitedialog.cpp + src/dminvitedialog.cpp src/inviteby.cpp src/joindialog.cpp src/settingsdialog.cpp diff --git a/tools/chatgui/src/dminvitedialog.cpp b/tools/chatgui/src/dminvitedialog.cpp new file mode 100644 index 00000000..2697eb6f --- /dev/null +++ b/tools/chatgui/src/dminvitedialog.cpp @@ -0,0 +1,101 @@ +#include "dminvitedialog.h" +#include "debug_ui.h" +#include "../db/db_manager.h" +#include "../transport/gui_bridge.h" +#include +#include +#include +#include +#include +#include +#include + +extern "C" { +#include "dm/dm_core.h" +#include "chat/chat_join.h" +#include "../../../lib/mem.h" +} + +DmInviteDialog::DmInviteDialog(DbManager* db, quint64 peer, const QString& name, const QString& source, QWidget* parent) + : QDialog(parent), m_peer(peer), m_name(name), m_source(source) { + setWindowTitle(QStringLiteral("Пригласить в группу")); + setMinimumWidth(400); + auto* layout = new QVBoxLayout(this); + layout->addWidget(new QLabel(QStringLiteral("Получатель: %1").arg(name), this)); + m_groups = new QComboBox(this); + for (const auto& group : db->getChannels()) m_groups->addItem(group.name, group.channelId); + m_groups->setCurrentIndex(-1); + m_groups->setPlaceholderText(QStringLiteral("Выберите группу")); + layout->addWidget(m_groups); + auto* expiry = new QLabel(QStringLiteral("Ссылка действует %1 минут с момента создания.\n" + "При поздней доставке PM потребуется новое приглашение.").arg(JOIN_KEY_TTL_SECONDS / 60), this); + expiry->setWordWrap(true); + layout->addWidget(expiry); + m_status = new QLabel(this); + m_status->setWordWrap(true); + if (m_groups->count() == 0) m_status->setText(QStringLiteral("Нет групп для приглашения.")); + layout->addWidget(m_status); + auto* buttons = new QDialogButtonBox(QDialogButtonBox::Cancel, this); + buttons->button(QDialogButtonBox::Cancel)->setText(QStringLiteral("Отмена")); + m_send = buttons->addButton(QStringLiteral("Пригласить"), QDialogButtonBox::AcceptRole); + m_send->setEnabled(false); + layout->addWidget(buttons); + connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject); + connect(m_send, &QPushButton::clicked, this, &DmInviteDialog::sendInvite); + connect(m_groups, QOverload::of(&QComboBox::currentIndexChanged), this, [this](int index) { + m_send->setEnabled(index >= 0 && !m_pending); + }); + gui_bridge_set_dm_invite_result_cb([](const uint8_t* data, int len) { + auto* dialog = qobject_cast(QApplication::activeModalWidget()); + if (dialog) dialog->onResult(data, len); + }); + GUI_INFO("invite PM dialog: opened peer=%llu source=%s groups=%d", (unsigned long long)peer, + qPrintable(source), m_groups->count()); +} + +DmInviteDialog::~DmInviteDialog() { + gui_bridge_set_dm_invite_result_cb(nullptr); + if (m_pending && gui_bridge_is_uasync_ready() && gui_bridge_get_inst()) { + auto* req = (struct dm_invite_req*)u_calloc(1, sizeof(struct dm_invite_req)); + if (!req) { GUI_ERROR("invite PM: cancel allocation failed"); return; } + req->inst = gui_bridge_get_inst(); req->request_id = m_requestId; req->cancel = 1; + gui_bridge_post_uasync_fn(dm_invite_trampoline, req); + } + GUI_INFO("invite PM dialog: closed peer=%llu pending=%d", (unsigned long long)m_peer, m_pending); +} + +void DmInviteDialog::sendInvite() { + if (m_pending || m_groups->currentIndex() < 0) return; + if (!gui_bridge_is_uasync_ready() || !gui_bridge_get_inst()) { + GUI_ERROR("invite PM: core unavailable"); + m_status->setText(QStringLiteral("Ядро не запущено.")); return; + } + auto* req = (struct dm_invite_req*)u_calloc(1, sizeof(struct dm_invite_req)); + if (!req) { GUI_ERROR("invite PM: request allocation failed"); m_status->setText(QStringLiteral("Недостаточно памяти.")); return; } + static uint64_t nextRequestId = 0; + req->inst = gui_bridge_get_inst(); req->request_id = m_requestId = ++nextRequestId; req->peer_node_id = m_peer; + req->channel_id = m_groups->currentData().toString().toULongLong(); + snprintf(req->source_ch_id, sizeof(req->source_ch_id), "%s", qPrintable(m_source)); + snprintf(req->peer_name, sizeof(req->peer_name), "%s", m_name.toUtf8().constData()); + m_pending = true; + m_groups->setEnabled(false); m_send->setEnabled(false); + m_status->setText(QStringLiteral("Создание и отправка приглашения…")); + gui_bridge_post_uasync_fn(dm_invite_trampoline, req); +} + +void DmInviteDialog::onResult(const uint8_t* data, int len) { + if (!data || len != 28) { GUI_ERROR("invite PM: invalid result bytes=%d", len); return; } + uint64_t request, peer, conv; int32_t result; + memcpy(&request, data, 8); memcpy(&result, data + 8, 4); + memcpy(&peer, data + 12, 8); memcpy(&conv, data + 20, 8); + if (!m_pending || request != m_requestId || peer != m_peer) return; + m_pending = false; + if (result == DM_INVITE_OK) { m_convId = QString::number(conv); accept(); return; } + const QString error = result == DM_INVITE_START_ERROR ? QStringLiteral("Не удалось создать личную беседу.") + : result == DM_INVITE_SEND_ERROR ? QStringLiteral("Не удалось отправить приглашение.") + : result == DM_INVITE_BUSY ? QStringLiteral("Другое приглашение ещё создаётся. Повторите попытку.") + : result == DM_INVITE_CANCELLED ? QStringLiteral("Создание приглашения отменено.") + : QStringLiteral("Не удалось создать приглашение. Проверьте подключение и повторите попытку."); + GUI_ERROR("invite PM dialog: failed request=%llu result=%d", (unsigned long long)request, result); + m_status->setText(error); m_groups->setEnabled(true); m_send->setEnabled(m_groups->currentIndex() >= 0); +} diff --git a/tools/chatgui/src/dminvitedialog.h b/tools/chatgui/src/dminvitedialog.h new file mode 100644 index 00000000..02023bc2 --- /dev/null +++ b/tools/chatgui/src/dminvitedialog.h @@ -0,0 +1,30 @@ +/* Выбор группы для приглашения фиксированного собеседника в PM. + * Закрытие отменяет ожидающую регистрацию; результат сопоставляется по request_id. */ +#pragma once + +#include +#include +#include + +class DbManager; +class QComboBox; +class QLabel; +class QPushButton; + +class DmInviteDialog : public QDialog { + Q_OBJECT +public: + DmInviteDialog(DbManager* db, quint64 peer, const QString& name, const QString& source, QWidget* parent = nullptr); + ~DmInviteDialog() override; + QString conversationId() const { return m_convId; } + +private: + void sendInvite(); + void onResult(const uint8_t* data, int len); + quint64 m_peer, m_requestId = 0; + QString m_name, m_source, m_convId; + QComboBox* m_groups; + QLabel* m_status; + QPushButton* m_send; + bool m_pending = false; +};