Add desktop invitation policy preview and member preservation dialog
This commit is contained in:
1 parent
ac5fbf0ffb
commit
a141fc7215
3 files changed
+131
No files matched your search
@@ -328,6 +328,7 @@ add_executable(vibechat WIN32
|
||||
src/creategroupdialog.cpp
|
||||
src/channeldelegate.cpp
|
||||
src/channelsettingsdialog.cpp
|
||||
src/grouppolicydialog.cpp
|
||||
src/chatview.cpp
|
||||
src/messagedelegate.cpp
|
||||
src/messagelist.cpp
|
||||
@@ -548,6 +549,7 @@ add_test(NAME test_channel_list COMMAND test_channel_list)
|
||||
set_tests_properties(test_channel_list PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 30)
|
||||
|
||||
add_executable(test_channel_settings tests/test_channel_settings.cpp src/channelsettingsdialog.cpp
|
||||
src/grouppolicydialog.cpp
|
||||
src/avatars/groupavatarmanager.cpp db/db_manager.cpp transport/gui_bridge_impl.cpp)
|
||||
target_link_libraries(test_channel_settings PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun utun_voice pthread ${CMAKE_DL_LIBS})
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||
@@ -753,6 +755,7 @@ add_test(NAME test_avatars COMMAND test_avatars)
|
||||
set_tests_properties(test_avatars PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=offscreen" TIMEOUT 60)
|
||||
|
||||
add_executable(test_group_avatars tests/test_group_avatars.cpp src/avatars/groupavatarmanager.cpp src/channelsettingsdialog.cpp
|
||||
src/grouppolicydialog.cpp
|
||||
src/channellist.cpp src/channeldelegate.cpp db/db_manager.cpp)
|
||||
target_link_libraries(test_group_avatars PRIVATE ${QT_LIBS} ${QT_TEST_LIB} utun pthread)
|
||||
add_test(NAME test_group_avatars COMMAND test_group_avatars)
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
#include "grouppolicydialog.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include <QApplication>
|
||||
#include <QComboBox>
|
||||
#include <QDialog>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QHash>
|
||||
#include <QJsonArray>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QLabel>
|
||||
#include <QListWidget>
|
||||
#include <QPointer>
|
||||
#include <QPushButton>
|
||||
#include <QTimer>
|
||||
#include <QVBoxLayout>
|
||||
#include <cstring>
|
||||
extern "C" {
|
||||
#include "chat/group_policy.h"
|
||||
#include "member_files/protocol.h"
|
||||
#include "mem.h"
|
||||
#include "debug_config.h"
|
||||
}
|
||||
|
||||
static QString text(const char* s) { return QCoreApplication::translate("ChatGui", s); }
|
||||
class GroupPolicyDialog;
|
||||
static QHash<quint64, QPointer<GroupPolicyDialog>> requests;
|
||||
static quint64 sequence = 0;
|
||||
|
||||
class GroupPolicyDialog : public QDialog {
|
||||
public:
|
||||
GroupPolicyDialog(const QString& channel, QWidget* parent) : QDialog(parent), channel(channel) {
|
||||
setWindowTitle(text("Who may invite members")); resize(530, 440);
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
mode = new QComboBox(this);
|
||||
mode->addItem(text("Owner and administrators only"), 0); mode->addItem(text("All group members"), 1);
|
||||
layout->addWidget(mode);
|
||||
auto* explanation = new QLabel(text("When permissions are restricted, members whose invitation chain no longer qualifies leave the group at the next synchronization. Select members below to re-sign with the group key and keep them."), this);
|
||||
explanation->setWordWrap(true); layout->addWidget(explanation);
|
||||
members = new QListWidget(this); layout->addWidget(members, 1);
|
||||
status = new QLabel(this); status->setWordWrap(true); status->setTextFormat(Qt::PlainText); layout->addWidget(status);
|
||||
auto* buttons = new QDialogButtonBox(QDialogButtonBox::Apply | QDialogButtonBox::Close, this);
|
||||
apply = buttons->button(QDialogButtonBox::Apply); layout->addWidget(buttons);
|
||||
connect(buttons, &QDialogButtonBox::rejected, this, &QDialog::reject);
|
||||
connect(apply, &QPushButton::clicked, this, [this] { request(true); });
|
||||
connect(mode, qOverload<int>(&QComboBox::currentIndexChanged), this, [this] { if (loaded) request(false); });
|
||||
timer.setSingleShot(true); timer.setInterval(15000);
|
||||
connect(&timer, &QTimer::timeout, this, [this] {
|
||||
requests.remove(pending); pending = 0; token.clear(); setBusy(false);
|
||||
status->setText(text("The request timed out. Reopen settings to check the result."));
|
||||
});
|
||||
request(false);
|
||||
}
|
||||
~GroupPolicyDialog() override { requests.remove(pending); }
|
||||
|
||||
void response(quint64 id, const QByteArray& bytes) {
|
||||
if (id != pending) return;
|
||||
requests.remove(pending); pending = 0; timer.stop();
|
||||
QJsonParseError error; const auto doc = QJsonDocument::fromJson(bytes, &error);
|
||||
const auto data = doc.object();
|
||||
if (error.error != QJsonParseError::NoError || !data.value("ok").toBool()) {
|
||||
token.clear(); setBusy(false);
|
||||
const auto reason = data.value("error").toString();
|
||||
status->setText(reason == "conflict" ? text("Membership changed. Review the refreshed list and apply again.") :
|
||||
reason == "sync_pending" ? text("Membership is synchronizing. Try again after synchronization.") : text("Could not change invitation permissions."));
|
||||
if (reason == "conflict") { conflict = true; request(false); }
|
||||
return;
|
||||
}
|
||||
canEdit = data.value("can_edit").toBool(); current = data.value("all_members").toBool();
|
||||
if (!loaded || data.value("applied").toBool()) {
|
||||
loaded = false; mode->setCurrentIndex(current ? 1 : 0); loaded = true;
|
||||
}
|
||||
token = data.value("token").toString(); members->clear();
|
||||
if (mode->currentIndex() != int(current) && !mode->currentIndex()) {
|
||||
for (const auto& value : data.value("affected").toArray()) {
|
||||
const auto member = value.toObject(); const auto node = member.value("node").toString();
|
||||
auto* item = new QListWidgetItem(member.value("name").toString() + " (" + node + ")", members);
|
||||
item->setData(Qt::UserRole, node); item->setCheckState(Qt::Unchecked);
|
||||
item->setToolTip(text("Invited by: %1").arg(member.value("inviter").toString()));
|
||||
}
|
||||
}
|
||||
status->setText(data.value("applied").toBool() ? text("Invitation permissions saved.") : conflict ?
|
||||
text("Membership changed. Review the refreshed list and apply again.") : canEdit ?
|
||||
text("Members affected: %1").arg(members->count()) : text("Only a superadministrator can change this setting."));
|
||||
conflict = false; setBusy(false);
|
||||
}
|
||||
private:
|
||||
void setBusy(bool busy) {
|
||||
mode->setEnabled(!busy && canEdit); members->setEnabled(!busy && canEdit);
|
||||
apply->setEnabled(!busy && canEdit && !token.isEmpty() && mode->currentIndex() != int(current));
|
||||
}
|
||||
void request(bool save) {
|
||||
if (pending) return;
|
||||
QJsonArray keep;
|
||||
if (save) for (int i = 0; i < members->count(); i++)
|
||||
if (members->item(i)->checkState() == Qt::Checked) keep.append(members->item(i)->data(Qt::UserRole).toString());
|
||||
const QByteArray selection = QJsonDocument(keep).toJson(QJsonDocument::Compact);
|
||||
auto* q = static_cast<group_policy_request*>(u_calloc(1, sizeof(group_policy_request) + size_t(selection.size()) + 1));
|
||||
if (!q) { status->setText(text("Could not change invitation permissions.")); return; }
|
||||
q->id = ++sequence; q->group = channel.toULongLong(); q->all_members = mode->currentIndex();
|
||||
if (save) { const auto t = token.toLatin1(); snprintf(q->token, sizeof(q->token), "%s", t.constData()); }
|
||||
memcpy(q->keep, selection.constData(), size_t(selection.size()));
|
||||
pending = q->id; requests.insert(pending, this); setBusy(true);
|
||||
if (gui_bridge_post_bound_fn(group_policy_gui_command, q, &q->inst)) {
|
||||
requests.remove(pending); pending = 0; u_free(q); setBusy(false);
|
||||
status->setText(text("The core is not running.")); return;
|
||||
}
|
||||
timer.start();
|
||||
}
|
||||
QString channel, token;
|
||||
QComboBox* mode; QListWidget* members; QLabel* status; QPushButton* apply;
|
||||
QTimer timer; quint64 pending = 0; bool loaded = false, current = false, canEdit = false, conflict = false;
|
||||
};
|
||||
|
||||
void openGroupPolicyDialog(const QString& channel, QWidget* parent) {
|
||||
gui_bridge_set_group_policy_result_cb([](const uint8_t* data, int size) {
|
||||
if (!data || size < 18) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "group policy invalid UI result bytes=%d", size); return; }
|
||||
const quint64 id = mf_get64(data); const QByteArray json(reinterpret_cast<const char*>(data + 16), size - 16);
|
||||
QMetaObject::invokeMethod(qApp, [id, json] { if (auto dialog = requests.value(id)) dialog->response(id, json); }, Qt::QueuedConnection);
|
||||
});
|
||||
auto* dialog = new GroupPolicyDialog(channel, parent);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowModality(Qt::WindowModal); dialog->show();
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
/* Общие права приглашения: асинхронный preview, выбор переподписания и подтверждённый commit ядра. */
|
||||
#pragma once
|
||||
#include <QString>
|
||||
class QWidget;
|
||||
void openGroupPolicyDialog(const QString& channel, QWidget* parent);
|
||||
Reference in new issue
Block a user