Refine device group controls and aggregate status indicators
This commit is contained in:
1 parent
03fea5eb74
commit
24cb0eb463
20 files changed
+442
-283
No files matched your search
@@ -1,5 +1,4 @@
|
||||
#include <QCoreApplication>
|
||||
#include <algorithm>
|
||||
#include "devgroupdialog.h"
|
||||
#include "../transport/gui_bridge.h"
|
||||
#include "debug_ui.h"
|
||||
@@ -8,7 +7,6 @@
|
||||
#include <QLabel>
|
||||
#include <QPushButton>
|
||||
#include <QLineEdit>
|
||||
#include <QListWidget>
|
||||
#include <QInputDialog>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
@@ -16,6 +14,7 @@
|
||||
#include <QTimer>
|
||||
#include <QPointer>
|
||||
#include <QHash>
|
||||
#include <QMessageBox>
|
||||
#include <QSet>
|
||||
#include <QDialogButtonBox>
|
||||
#include <QRegularExpressionValidator>
|
||||
@@ -25,7 +24,7 @@ extern "C" {
|
||||
}
|
||||
class DevgroupDialog;
|
||||
static QPointer<DevgroupDialog> active;
|
||||
static QList<QPointer<QPushButton>> buttons;
|
||||
static QHash<quint64, QPointer<QPushButton>> leaveRequests;
|
||||
static quint64 nextRequest = 0;
|
||||
static QPointer<QWidget> invitationWindow;
|
||||
static quint64 statusRequest = 0, lastAutomaticSnapshot = 0;
|
||||
@@ -39,35 +38,19 @@ class DevgroupDialog : public QDialog {
|
||||
public:
|
||||
static QString tr(const char* text) { return QCoreApplication::translate("DevgroupDialog", text); }
|
||||
void refresh() { send(CHAT_DEVGROUP_STATUS); }
|
||||
DevgroupDialog(QWidget* parent, const QString& channel, quint64 peer, bool inviting = false) : QDialog(parent), channel(channel), peer(peer), inviting(inviting) {
|
||||
setAttribute(Qt::WA_DeleteOnClose); setModal(inviting); setWindowTitle(inviting ? tr("Add my device") : tr("My devices")); resize(480, 360);
|
||||
DevgroupDialog(QWidget* parent, const QString& channel, quint64 peer) : QDialog(parent), channel(channel), peer(peer) {
|
||||
setAttribute(Qt::WA_DeleteOnClose); setModal(true); setWindowTitle(tr("Add device")); resize(420, 140);
|
||||
auto* layout = new QVBoxLayout(this);
|
||||
title = new QLabel(this); layout->addWidget(title);
|
||||
name = new QLineEdit(this); name->setPlaceholderText(tr("Device group name")); layout->addWidget(name);
|
||||
auto* rename = new QPushButton(tr("Create / rename"), this); layout->addWidget(rename);
|
||||
connect(rename, &QPushButton::clicked, this, [this] { send(CHAT_DEVGROUP_RENAME, name->text()); });
|
||||
if (peer && !inviting) {
|
||||
auto* invite = new QPushButton(tr("Add device %1").arg(QString::number(peer, 16)), this); layout->addWidget(invite);
|
||||
connect(invite, &QPushButton::clicked, this, [this] { send(CHAT_DEVGROUP_INVITE); });
|
||||
}
|
||||
status = new QLabel(this); status->setWordWrap(true); status->setTextInteractionFlags(Qt::TextSelectableByMouse); layout->addWidget(status);
|
||||
list = new QListWidget(this); layout->addWidget(list);
|
||||
auto* accept = new QPushButton(tr("Enter PIN for selected invitation"), this); layout->addWidget(accept);
|
||||
connect(accept, &QPushButton::clicked, this, [this] {
|
||||
const auto* item = list->currentItem(); if (!item || item->data(Qt::UserRole).toString().isEmpty()) return;
|
||||
const QString id = item->data(Qt::UserRole).toString(); bool ok = false;
|
||||
const QString pin = QInputDialog::getText(this, tr("Add device"), tr("PIN shown on the inviting device"), QLineEdit::Normal, {}, &ok);
|
||||
if (ok) send(CHAT_DEVGROUP_ACCEPT, id, pin);
|
||||
});
|
||||
auto* leave = new QPushButton(tr("Leave device group"), this); layout->addWidget(leave);
|
||||
connect(leave, &QPushButton::clicked, this, [this] { send(CHAT_DEVGROUP_LEAVE); });
|
||||
status = new QLabel(tr("Waiting for PIN"), this); status->setWordWrap(true);
|
||||
status->setTextInteractionFlags(Qt::TextSelectableByMouse); layout->addWidget(status);
|
||||
auto* close = new QDialogButtonBox(QDialogButtonBox::Close, this); layout->addWidget(close);
|
||||
connect(close, &QDialogButtonBox::rejected, this, &QDialog::close);
|
||||
auto* timer = new QTimer(this); timer->setInterval(1000);
|
||||
connect(timer, &QTimer::timeout, this, [this] { if (!gui_bridge_is_uasync_ready()) pending.clear();
|
||||
else if (!pending.values().contains(CHAT_DEVGROUP_STATUS)) send(CHAT_DEVGROUP_STATUS); });
|
||||
if (inviting) {
|
||||
title->hide(); name->hide(); rename->hide(); list->hide(); accept->hide(); leave->hide();
|
||||
}
|
||||
timer->start(); send(inviting ? CHAT_DEVGROUP_INVITE : CHAT_DEVGROUP_STATUS);
|
||||
connect(timer, &QTimer::timeout, this, [this] {
|
||||
if (!gui_bridge_is_uasync_ready()) { pending.clear(); status->setText(tr("Core unavailable")); }
|
||||
else if (!pending.values().contains(CHAT_DEVGROUP_STATUS)) refresh();
|
||||
});
|
||||
timer->start(); send(CHAT_DEVGROUP_INVITE);
|
||||
}
|
||||
void result(const uint8_t* data, int len) {
|
||||
if (len < 9) return;
|
||||
@@ -80,52 +63,45 @@ public:
|
||||
outgoingId = json["id"].toString(); pinText = tr("PIN: %1 — enter it on the other device within 2 minutes.").arg(json["pin"].toString());
|
||||
status->setText(pinText); return;
|
||||
}
|
||||
title->setText(json["group"].toString().isEmpty() ? tr("No device group") : json["name"].toString());
|
||||
const QString selected = list->currentItem() ? list->currentItem()->data(Qt::UserRole).toString() : QString();
|
||||
list->clear();
|
||||
const QStringList states = {{}, tr("Waiting for PIN"), tr("Checking PIN"), tr("Transferring keys"), tr("Joined"),
|
||||
tr("Rejected"), tr("Expired"), tr("Result unknown")};
|
||||
for (const auto value : json["invitations"].toArray()) {
|
||||
const auto invitation = value.toObject(); const int state = invitation["state"].toInt();
|
||||
if (inviting && invitation["id"].toString() == outgoingId) {
|
||||
status->setText((state < 4 ? pinText + "\n" : QString()) + states.value(state));
|
||||
}
|
||||
auto* item = new QListWidgetItem(invitation["peer"].toString() + " — " + states.value(state), list);
|
||||
if (!invitation["outgoing"].toInt() && state == 1) {
|
||||
item->setData(Qt::UserRole, invitation["id"].toString());
|
||||
if (item->data(Qt::UserRole).toString() == selected) list->setCurrentItem(item);
|
||||
}
|
||||
if (invitation["id"].toString() == outgoingId) status->setText((state < 4 ? pinText + "\n" : QString()) + states.value(state));
|
||||
}
|
||||
}
|
||||
private:
|
||||
void send(chat_devgroup_action action, const QString& text = {}, const QString& pin = {}) {
|
||||
const auto bytes = text.toUtf8(), code = pin.toUtf8();
|
||||
if (bytes.size() >= 128 || bytes.contains('\0') || code.size() >= 5 || code.contains('\0')) {
|
||||
status->setText(tr("Invalid name or PIN")); return;
|
||||
}
|
||||
void send(chat_devgroup_action action) {
|
||||
auto* req = static_cast<chat_devgroup_request*>(u_calloc(1, sizeof(chat_devgroup_request)));
|
||||
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "devgroup UI: allocation failed"); return; }
|
||||
req->inst = gui_bridge_get_inst(); req->request_id = ++nextRequest; req->group = channel.toULongLong(); req->peer = peer;
|
||||
req->action = action; memcpy(req->text, bytes.constData(), bytes.size()); memcpy(req->pin, code.constData(), code.size());
|
||||
const quint64 id = req->request_id;
|
||||
req->action = action; const quint64 id = req->request_id;
|
||||
if (gui_bridge_post_uasync_owned_fn(chat_devgroup_trampoline, req) < 0) {
|
||||
memset(req, 0, sizeof(*req)); u_free(req); status->setText(tr("Core unavailable"));
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "devgroup UI: core unavailable"); return;
|
||||
u_free(req); status->setText(tr("Core unavailable")); DEBUG_WARN(DEBUG_CATEGORY_CHAT, "devgroup UI: core unavailable"); return;
|
||||
}
|
||||
pending.insert(id, action);
|
||||
}
|
||||
QString channel, outgoingId, pinText; quint64 peer; bool inviting;
|
||||
QLabel *title, *status; QLineEdit* name; QListWidget* list;
|
||||
QString channel, outgoingId, pinText; quint64 peer;
|
||||
QLabel* status;
|
||||
QHash<quint64, int> pending;
|
||||
};
|
||||
static void devgroupResult(const uint8_t* data, int len) {
|
||||
if (!len) {
|
||||
refreshInvitations();
|
||||
for (const auto& button : buttons) if (button) button->setText(QCoreApplication::translate("AccountList", "My devices") + " •");
|
||||
if (active) active->refresh();
|
||||
} else {
|
||||
if (len >= 9) {
|
||||
quint64 id = 0; for (int i = 0; i < 8; ++i) id |= quint64(data[i]) << (8*i);
|
||||
if (leaveRequests.contains(id)) {
|
||||
const auto button = leaveRequests.take(id);
|
||||
if (!data[8]) {
|
||||
DEBUG_WARN(DEBUG_CATEGORY_CHAT, "devgroup UI: disconnect refused");
|
||||
if (button) { button->setEnabled(true); QMessageBox::warning(button->window(),
|
||||
QCoreApplication::translate("ChatGui", "Disconnect from my devices"),
|
||||
DevgroupDialog::tr("Operation failed. Check connection and invitation status.")); }
|
||||
} else DEBUG_INFO(DEBUG_CATEGORY_CHAT, "devgroup UI: own device disconnected");
|
||||
return;
|
||||
}
|
||||
const bool automatic = id == statusRequest || acceptRequests.contains(id);
|
||||
if (id == statusRequest) statusRequest = 0;
|
||||
if (acceptRequests.contains(id)) awaitingPins.remove(acceptRequests.take(id));
|
||||
@@ -140,16 +116,6 @@ static void devgroupResult(const uint8_t* data, int len) {
|
||||
if (active) active->result(data, len);
|
||||
}
|
||||
}
|
||||
void watchDevgroupButton(QPushButton* button) {
|
||||
buttons.erase(std::remove_if(buttons.begin(), buttons.end(), [](const QPointer<QPushButton>& b) { return b.isNull(); }), buttons.end());
|
||||
buttons.append(button); gui_bridge_set_devgroup_result_cb(devgroupResult);
|
||||
}
|
||||
void showDevgroupDialog(QWidget* parent, const QString& channel, quint64 peer) {
|
||||
if (active) { active->close(); active = nullptr; }
|
||||
gui_bridge_set_devgroup_result_cb(devgroupResult);
|
||||
active = new DevgroupDialog(parent, channel, peer); active->show();
|
||||
}
|
||||
|
||||
static void refreshInvitations() {
|
||||
if (!invitationWindow || statusRequest || !gui_bridge_is_uasync_ready()) return;
|
||||
auto* req = static_cast<chat_devgroup_request*>(u_calloc(1, sizeof(chat_devgroup_request)));
|
||||
@@ -171,7 +137,7 @@ static void automaticInvitations(const QJsonObject& snapshot) {
|
||||
if (pinDialogs.value(id) || dismissedInvites.contains(id) || awaitingPins.contains(id)) continue;
|
||||
auto* dialog = new QInputDialog(invitationWindow); pinDialogs.insert(id, dialog);
|
||||
dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setObjectName("devgroupIncomingPin");
|
||||
dialog->setWindowTitle(DevgroupDialog::tr("Add my device"));
|
||||
dialog->setWindowTitle(DevgroupDialog::tr("Add device"));
|
||||
dialog->setLabelText(entry["peer"].toString() + "\n" + DevgroupDialog::tr(attemptedPins.contains(id) ?
|
||||
"PIN was not accepted. Try again." : "PIN shown on the inviting device"));
|
||||
dialog->setInputMode(QInputDialog::TextInput); dialog->setWindowModality(Qt::ApplicationModal);
|
||||
@@ -222,5 +188,18 @@ void initDevgroupUi(QWidget* window) {
|
||||
void inviteMyDevice(QWidget* parent, const QString& channel, quint64 peer) {
|
||||
if (active) { active->close(); active = nullptr; }
|
||||
gui_bridge_set_devgroup_result_cb(devgroupResult);
|
||||
active = new DevgroupDialog(parent, channel, peer, true); active->show();
|
||||
active = new DevgroupDialog(parent, channel, peer); active->show();
|
||||
}
|
||||
|
||||
void disconnectMyDevice(QPushButton* button) {
|
||||
auto* req = static_cast<chat_devgroup_request*>(u_calloc(1, sizeof(chat_devgroup_request)));
|
||||
if (!req) { DEBUG_ERROR(DEBUG_CATEGORY_CHAT, "devgroup UI: disconnect allocation failed"); return; }
|
||||
req->inst = gui_bridge_get_inst(); req->action = CHAT_DEVGROUP_LEAVE; req->request_id = ++nextRequest;
|
||||
const quint64 id = req->request_id; gui_bridge_set_devgroup_result_cb(devgroupResult);
|
||||
if (gui_bridge_post_uasync_owned_fn(chat_devgroup_trampoline, req) < 0) {
|
||||
u_free(req); DEBUG_WARN(DEBUG_CATEGORY_CHAT, "devgroup UI: disconnect enqueue failed");
|
||||
QMessageBox::warning(button->window(), QCoreApplication::translate("ChatGui", "Disconnect from my devices"),
|
||||
DevgroupDialog::tr("Core unavailable")); return;
|
||||
}
|
||||
leaveRequests.insert(id, button); button->setEnabled(false);
|
||||
}
|
||||
Reference in new issue
Block a user