From b70f37194fd968950c920b1c39388154aa46cdc1 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Wed, 10 Feb 2021 21:22:42 +0530 Subject: ui almost looks the same, midway between transition from old room settings to new room settings --- src/MainWindow.cpp | 4 +- src/MainWindow.h | 2 +- src/dialogs/RoomSettings.cpp | 865 ----------------------------------- src/dialogs/RoomSettings.h | 150 ------ src/dialogs/RoomSettingsOld.cpp | 865 +++++++++++++++++++++++++++++++++++ src/dialogs/RoomSettingsOld.h | 150 ++++++ src/timeline/TimelineViewManager.cpp | 4 +- src/timeline/TimelineViewManager.h | 5 +- src/ui/RoomSettings.cpp | 33 ++ src/ui/RoomSettings.h | 25 + 10 files changed, 1082 insertions(+), 1021 deletions(-) delete mode 100644 src/dialogs/RoomSettings.cpp delete mode 100644 src/dialogs/RoomSettings.h create mode 100644 src/dialogs/RoomSettingsOld.cpp create mode 100644 src/dialogs/RoomSettingsOld.h create mode 100644 src/ui/RoomSettings.cpp create mode 100644 src/ui/RoomSettings.h (limited to 'src') diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index ab3c2cf2..b1635c94 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -51,7 +51,7 @@ #include "dialogs/Logout.h" #include "dialogs/MemberList.h" #include "dialogs/ReadReceipts.h" -#include "dialogs/RoomSettings.h" +#include "dialogs/RoomSettingsOld.h" MainWindow *MainWindow::instance_ = nullptr; @@ -366,7 +366,7 @@ MainWindow::hasActiveUser() void MainWindow::openRoomSettings(const QString &room_id) { - auto dialog = new dialogs::RoomSettings(room_id, this); + auto dialog = new dialogs::RoomSettingsOld(room_id, this); showDialog(dialog); } diff --git a/src/MainWindow.h b/src/MainWindow.h index bb219813..b3983d72 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -54,7 +54,7 @@ class LeaveRoom; class Logout; class MemberList; class ReCaptcha; -class RoomSettings; +class RoomSettingsOld; } class MainWindow : public QMainWindow diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp deleted file mode 100644 index bd3cc26f..00000000 --- a/src/dialogs/RoomSettings.cpp +++ /dev/null @@ -1,865 +0,0 @@ -#include "dialogs/RoomSettings.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "Cache.h" -#include "ChatPage.h" -#include "Config.h" -#include "Logging.h" -#include "MatrixClient.h" -#include "Utils.h" -#include "ui/Avatar.h" -#include "ui/FlatButton.h" -#include "ui/LoadingIndicator.h" -#include "ui/Painter.h" -#include "ui/TextField.h" -#include "ui/ToggleButton.h" - -using namespace dialogs; -using namespace mtx::events; - -constexpr int BUTTON_SIZE = 36; -constexpr int BUTTON_RADIUS = BUTTON_SIZE / 2; -constexpr int WIDGET_MARGIN = 20; -constexpr int TOP_WIDGET_MARGIN = 2 * WIDGET_MARGIN; -constexpr int WIDGET_SPACING = 15; -constexpr int TEXT_SPACING = 4; -constexpr int BUTTON_SPACING = 2 * TEXT_SPACING; - -bool -ClickableFilter::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::MouseButtonRelease) { - emit clicked(); - return true; - } - - return QObject::eventFilter(obj, event); -} - -EditModal::EditModal(const QString &roomId, QWidget *parent) - : QWidget(parent) - , roomId_{roomId} -{ - setAutoFillBackground(true); - setAttribute(Qt::WA_DeleteOnClose, true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - - QFont largeFont; - largeFont.setPointSizeF(largeFont.pointSizeF() * 1.4); - setMinimumWidth(conf::window::minModalWidth); - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - - auto layout = new QVBoxLayout(this); - - applyBtn_ = new QPushButton(tr("Apply"), this); - cancelBtn_ = new QPushButton(tr("Cancel"), this); - cancelBtn_->setDefault(true); - - auto btnLayout = new QHBoxLayout; - btnLayout->addStretch(1); - btnLayout->setSpacing(15); - btnLayout->addWidget(cancelBtn_); - btnLayout->addWidget(applyBtn_); - - nameInput_ = new TextField(this); - nameInput_->setLabel(tr("Name").toUpper()); - topicInput_ = new TextField(this); - topicInput_->setLabel(tr("Topic").toUpper()); - - errorField_ = new QLabel(this); - errorField_->setWordWrap(true); - errorField_->hide(); - - layout->addWidget(nameInput_); - layout->addWidget(topicInput_); - layout->addLayout(btnLayout, 1); - - auto labelLayout = new QHBoxLayout; - labelLayout->setAlignment(Qt::AlignHCenter); - labelLayout->addWidget(errorField_); - layout->addLayout(labelLayout); - - connect(applyBtn_, &QPushButton::clicked, this, &EditModal::applyClicked); - connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close); - - auto window = QApplication::activeWindow(); - auto center = window->frameGeometry().center(); - move(center.x() - (width() * 0.5), center.y() - (height() * 0.5)); -} - -void -EditModal::topicEventSent() -{ - errorField_->hide(); - close(); -} - -void -EditModal::nameEventSent(const QString &name) -{ - errorField_->hide(); - emit nameChanged(name); - close(); -} - -void -EditModal::error(const QString &msg) -{ - errorField_->setText(msg); - errorField_->show(); -} - -void -EditModal::applyClicked() -{ - // Check if the values are changed from the originals. - auto newName = nameInput_->text().trimmed(); - auto newTopic = topicInput_->text().trimmed(); - - errorField_->hide(); - - if (newName == initialName_ && newTopic == initialTopic_) { - close(); - return; - } - - using namespace mtx::events; - auto proxy = std::make_shared(); - connect(proxy.get(), &ThreadProxy::topicEventSent, this, &EditModal::topicEventSent); - connect(proxy.get(), &ThreadProxy::nameEventSent, this, &EditModal::nameEventSent); - connect(proxy.get(), &ThreadProxy::error, this, &EditModal::error); - - if (newName != initialName_ && !newName.isEmpty()) { - state::Name body; - body.name = newName.toStdString(); - - http::client()->send_state_event( - roomId_.toStdString(), - body, - [proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) { - if (err) { - emit proxy->error( - QString::fromStdString(err->matrix_error.error)); - return; - } - - emit proxy->nameEventSent(newName); - }); - } - - if (newTopic != initialTopic_ && !newTopic.isEmpty()) { - state::Topic body; - body.topic = newTopic.toStdString(); - - http::client()->send_state_event( - roomId_.toStdString(), - body, - [proxy](const mtx::responses::EventId &, mtx::http::RequestErr err) { - if (err) { - emit proxy->error( - QString::fromStdString(err->matrix_error.error)); - return; - } - - emit proxy->topicEventSent(); - }); - } -} - -void -EditModal::setFields(const QString &roomName, const QString &roomTopic) -{ - initialName_ = roomName; - initialTopic_ = roomTopic; - - nameInput_->setText(roomName); - topicInput_->setText(roomTopic); -} - -RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) - : QFrame(parent) - , room_id_{std::move(room_id)} -{ - retrieveRoomInfo(); - - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setWindowModality(Qt::WindowModal); - setAttribute(Qt::WA_DeleteOnClose, true); - - QFont largeFont; - largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5); - - setMinimumWidth(conf::window::minModalWidth); - setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); - - auto layout = new QVBoxLayout(this); - layout->setSpacing(WIDGET_SPACING); - layout->setContentsMargins(WIDGET_MARGIN, TOP_WIDGET_MARGIN, WIDGET_MARGIN, WIDGET_MARGIN); - - QFont font; - font.setWeight(QFont::Medium); - auto settingsLabel = new QLabel(tr("Settings").toUpper(), this); - settingsLabel->setFont(font); - - auto infoLabel = new QLabel(tr("Info").toUpper(), this); - infoLabel->setFont(font); - - QFont monospaceFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); - - auto roomIdLabel = new QLabel(room_id, this); - roomIdLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); - roomIdLabel->setFont(monospaceFont); - - auto roomIdLayout = new QHBoxLayout; - roomIdLayout->setMargin(0); - roomIdLayout->addWidget(new QLabel(tr("Internal ID"), this), - Qt::AlignBottom | Qt::AlignLeft); - roomIdLayout->addWidget(roomIdLabel, 0, Qt::AlignBottom | Qt::AlignRight); - - auto roomVersionLabel = new QLabel(QString::fromStdString(info_.version), this); - roomVersionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); - roomVersionLabel->setFont(monospaceFont); - - auto roomVersionLayout = new QHBoxLayout; - roomVersionLayout->setMargin(0); - roomVersionLayout->addWidget(new QLabel(tr("Room Version"), this), - Qt::AlignBottom | Qt::AlignLeft); - roomVersionLayout->addWidget(roomVersionLabel, 0, Qt::AlignBottom | Qt::AlignRight); - - auto notifLabel = new QLabel(tr("Notifications"), this); - notifCombo = new QComboBox(this); - notifCombo->addItem(tr( - "Muted")); //{"conditions":[{"kind":"event_match","key":"room_id","pattern":"!jxlRxnrZCsjpjDubDX:matrix.org"}],"actions":["dont_notify"]} - notifCombo->addItem(tr("Mentions only")); // {"actions":["dont_notify"]} - notifCombo->addItem(tr("All messages")); // delete rule - - connect(this, &RoomSettings::notifChanged, notifCombo, &QComboBox::setCurrentIndex); - http::client()->get_pushrules( - "global", - "override", - room_id_.toStdString(), - [this](const mtx::pushrules::PushRule &rule, mtx::http::RequestErr &err) { - if (err) { - if (err->status_code == boost::beast::http::status::not_found) - http::client()->get_pushrules( - "global", - "room", - room_id_.toStdString(), - [this](const mtx::pushrules::PushRule &rule, - mtx::http::RequestErr &err) { - if (err) { - emit notifChanged(2); // all messages - return; - } - - if (rule.enabled) - emit notifChanged(1); // mentions only - }); - return; - } - - if (rule.enabled) - emit notifChanged(0); // muted - else - emit notifChanged(2); // all messages - }); - - connect(notifCombo, QOverload::of(&QComboBox::activated), [this](int index) { - std::string room_id = room_id_.toStdString(); - if (index == 0) { - // mute room - // delete old rule first, then add new rule - mtx::pushrules::PushRule rule; - rule.actions = {mtx::pushrules::actions::dont_notify{}}; - mtx::pushrules::PushCondition condition; - condition.kind = "event_match"; - condition.key = "room_id"; - condition.pattern = room_id; - rule.conditions = {condition}; - - http::client()->put_pushrules( - "global", - "override", - room_id, - rule, - [room_id](mtx::http::RequestErr &err) { - if (err) - nhlog::net()->error( - "failed to set pushrule for room {}: {} {}", - room_id, - static_cast(err->status_code), - err->matrix_error.error); - http::client()->delete_pushrules( - "global", "room", room_id, [room_id](mtx::http::RequestErr &) { - }); - }); - } else if (index == 1) { - // mentions only - // delete old rule first, then add new rule - mtx::pushrules::PushRule rule; - rule.actions = {mtx::pushrules::actions::dont_notify{}}; - http::client()->put_pushrules( - "global", "room", room_id, rule, [room_id](mtx::http::RequestErr &err) { - if (err) - nhlog::net()->error( - "failed to set pushrule for room {}: {} {}", - room_id, - static_cast(err->status_code), - err->matrix_error.error); - http::client()->delete_pushrules( - "global", - "override", - room_id, - [room_id](mtx::http::RequestErr &) {}); - }); - } else { - // all messages - http::client()->delete_pushrules( - "global", "override", room_id, [room_id](mtx::http::RequestErr &) { - http::client()->delete_pushrules( - "global", "room", room_id, [room_id](mtx::http::RequestErr &) { - }); - }); - } - }); - - auto notifOptionLayout_ = new QHBoxLayout; - notifOptionLayout_->setMargin(0); - notifOptionLayout_->addWidget(notifLabel, Qt::AlignBottom | Qt::AlignLeft); - notifOptionLayout_->addWidget(notifCombo, 0, Qt::AlignBottom | Qt::AlignRight); - - auto accessLabel = new QLabel(tr("Room access"), this); - accessCombo = new QComboBox(this); - accessCombo->addItem(tr("Anyone and guests")); - accessCombo->addItem(tr("Anyone")); - accessCombo->addItem(tr("Invited users")); - accessCombo->setDisabled( - !canChangeJoinRules(room_id_.toStdString(), utils::localUser().toStdString())); - connect(accessCombo, QOverload::of(&QComboBox::activated), [this](int index) { - using namespace mtx::events::state; - - auto guest_access = [](int index) -> state::GuestAccess { - state::GuestAccess event; - - if (index == 0) - event.guest_access = state::AccessState::CanJoin; - else - event.guest_access = state::AccessState::Forbidden; - - return event; - }(index); - - auto join_rule = [](int index) -> state::JoinRules { - state::JoinRules event; - - switch (index) { - case 0: - case 1: - event.join_rule = state::JoinRule::Public; - break; - default: - event.join_rule = state::JoinRule::Invite; - } - - return event; - }(index); - - updateAccessRules(room_id_.toStdString(), join_rule, guest_access); - }); - - if (info_.join_rule == state::JoinRule::Public) { - if (info_.guest_access) { - accessCombo->setCurrentIndex(0); - } else { - accessCombo->setCurrentIndex(1); - } - } else { - accessCombo->setCurrentIndex(2); - } - - auto accessOptionLayout = new QHBoxLayout(); - accessOptionLayout->setMargin(0); - accessOptionLayout->addWidget(accessLabel, Qt::AlignBottom | Qt::AlignLeft); - accessOptionLayout->addWidget(accessCombo, 0, Qt::AlignBottom | Qt::AlignRight); - - auto encryptionLabel = new QLabel(tr("Encryption"), this); - encryptionToggle_ = new Toggle(this); - - auto encryptionOptionLayout = new QHBoxLayout; - encryptionOptionLayout->setMargin(0); - encryptionOptionLayout->addWidget(encryptionLabel, Qt::AlignBottom | Qt::AlignLeft); - encryptionOptionLayout->addWidget(encryptionToggle_, 0, Qt::AlignBottom | Qt::AlignRight); - - auto keyRequestsLabel = new QLabel(tr("Respond to key requests"), this); - keyRequestsLabel->setToolTipDuration(6000); - keyRequestsLabel->setToolTip( - tr("Whether or not the client should respond automatically with the session keys\n" - " upon request. Use with caution, this is a temporary measure to test the\n" - " E2E implementation until device verification is completed.")); - keyRequestsToggle_ = new Toggle(this); - connect(keyRequestsToggle_, &Toggle::toggled, this, [this](bool isOn) { - utils::setKeyRequestsPreference(room_id_, isOn); - }); - - auto keyRequestsLayout = new QHBoxLayout; - keyRequestsLayout->setMargin(0); - keyRequestsLayout->setSpacing(0); - keyRequestsLayout->addWidget(keyRequestsLabel, Qt::AlignBottom | Qt::AlignLeft); - keyRequestsLayout->addWidget(keyRequestsToggle_, 0, Qt::AlignBottom | Qt::AlignRight); - - connect(encryptionToggle_, &Toggle::toggled, this, [this, keyRequestsLabel](bool isOn) { - if (!isOn || usesEncryption_) - return; - - QMessageBox msgBox; - msgBox.setIcon(QMessageBox::Question); - msgBox.setWindowTitle(tr("End-to-End Encryption")); - msgBox.setText(tr( - "Encryption is currently experimental and things might break unexpectedly.
" - "Please take note that it can't be disabled afterwards.")); - msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); - msgBox.setDefaultButton(QMessageBox::Save); - int ret = msgBox.exec(); - - switch (ret) { - case QMessageBox::Ok: { - encryptionToggle_->setState(true); - encryptionToggle_->setEnabled(false); - enableEncryption(); - keyRequestsToggle_->show(); - keyRequestsLabel->show(); - break; - } - default: { - break; - } - } - }); - - // Disable encryption button. - if (usesEncryption_) { - encryptionToggle_->setState(true); - encryptionToggle_->setEnabled(false); - - keyRequestsToggle_->setState(utils::respondsToKeyRequests(room_id_)); - } else { - encryptionToggle_->setState(false); - - keyRequestsLabel->hide(); - keyRequestsToggle_->hide(); - } - - // Hide encryption option for public rooms. - if (!usesEncryption_ && (info_.join_rule == state::JoinRule::Public)) { - encryptionToggle_->hide(); - encryptionLabel->hide(); - - keyRequestsLabel->hide(); - keyRequestsToggle_->hide(); - } - - avatar_ = new Avatar(this, 128); - avatar_->setLetter(utils::firstChar(QString::fromStdString(info_.name))); - if (!info_.avatar_url.empty()) - avatar_->setImage(QString::fromStdString(info_.avatar_url)); - - if (canChangeAvatar(room_id_.toStdString(), utils::localUser().toStdString())) { - auto filter = new ClickableFilter(this); - avatar_->installEventFilter(filter); - avatar_->setCursor(Qt::PointingHandCursor); - connect(filter, &ClickableFilter::clicked, this, &RoomSettings::updateAvatar); - } - - roomNameLabel_ = new QLabel(QString::fromStdString(info_.name), this); - roomNameLabel_->setFont(largeFont); - - auto membersLabel = new QLabel(tr("%n member(s)", "", (int)info_.member_count), this); - - auto textLayout = new QVBoxLayout; - textLayout->addWidget(roomNameLabel_); - textLayout->addWidget(membersLabel); - textLayout->setAlignment(roomNameLabel_, Qt::AlignCenter | Qt::AlignTop); - textLayout->setAlignment(membersLabel, Qt::AlignCenter | Qt::AlignTop); - textLayout->setSpacing(TEXT_SPACING); - textLayout->setMargin(0); - - setupEditButton(); - - errorLabel_ = new QLabel(this); - errorLabel_->setAlignment(Qt::AlignCenter); - errorLabel_->hide(); - - spinner_ = new LoadingIndicator(this); - spinner_->setFixedHeight(30); - spinner_->setFixedWidth(30); - spinner_->hide(); - auto spinnerLayout = new QVBoxLayout; - spinnerLayout->addWidget(spinner_); - spinnerLayout->setAlignment(Qt::AlignCenter); - spinnerLayout->setMargin(0); - spinnerLayout->setSpacing(0); - - auto okBtn = new QPushButton("OK", this); - - auto buttonLayout = new QHBoxLayout(); - buttonLayout->setSpacing(15); - buttonLayout->addStretch(1); - buttonLayout->addWidget(okBtn); - - layout->addWidget(avatar_, Qt::AlignCenter | Qt::AlignTop); - layout->addLayout(textLayout); - layout->addLayout(btnLayout_); - layout->addWidget(settingsLabel, Qt::AlignLeft); - layout->addLayout(notifOptionLayout_); - layout->addLayout(accessOptionLayout); - layout->addLayout(encryptionOptionLayout); - layout->addLayout(keyRequestsLayout); - layout->addWidget(infoLabel, Qt::AlignLeft); - layout->addLayout(roomIdLayout); - layout->addLayout(roomVersionLayout); - layout->addWidget(errorLabel_); - layout->addLayout(buttonLayout); - layout->addLayout(spinnerLayout); - layout->addStretch(1); - - connect(this, &RoomSettings::enableEncryptionError, this, [this](const QString &msg) { - encryptionToggle_->setState(false); - keyRequestsToggle_->setState(false); - keyRequestsToggle_->setEnabled(false); - keyRequestsToggle_->hide(); - - emit ChatPage::instance()->showNotification(msg); - }); - - connect(this, &RoomSettings::showErrorMessage, this, [this](const QString &msg) { - if (!errorLabel_) - return; - - stopLoadingSpinner(); - - errorLabel_->show(); - errorLabel_->setText(msg); - }); - - connect(this, &RoomSettings::accessRulesUpdated, this, [this]() { - stopLoadingSpinner(); - resetErrorLabel(); - }); - - auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); - connect(closeShortcut, &QShortcut::activated, this, &RoomSettings::close); - connect(okBtn, &QPushButton::clicked, this, &RoomSettings::close); -} - -void -RoomSettings::setupEditButton() -{ - btnLayout_ = new QHBoxLayout; - btnLayout_->setSpacing(BUTTON_SPACING); - btnLayout_->setMargin(0); - - if (!canChangeNameAndTopic(room_id_.toStdString(), utils::localUser().toStdString())) - return; - - QIcon editIcon; - editIcon.addFile(":/icons/icons/ui/edit.png"); - editFieldsBtn_ = new FlatButton(this); - editFieldsBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE); - editFieldsBtn_->setCornerRadius(BUTTON_RADIUS); - editFieldsBtn_->setIcon(editIcon); - editFieldsBtn_->setIcon(editIcon); - editFieldsBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS)); - - connect(editFieldsBtn_, &QPushButton::clicked, this, [this]() { - retrieveRoomInfo(); - - auto modal = new EditModal(room_id_, this); - modal->setFields(QString::fromStdString(info_.name), - QString::fromStdString(info_.topic)); - modal->raise(); - modal->show(); - connect(modal, &EditModal::nameChanged, this, [this](const QString &newName) { - if (roomNameLabel_) - roomNameLabel_->setText(newName); - }); - }); - - btnLayout_->addStretch(1); - btnLayout_->addWidget(editFieldsBtn_); - btnLayout_->addStretch(1); -} - -void -RoomSettings::retrieveRoomInfo() -{ - try { - usesEncryption_ = cache::isRoomEncrypted(room_id_.toStdString()); - info_ = cache::singleRoomInfo(room_id_.toStdString()); - setAvatar(); - } catch (const lmdb::error &) { - nhlog::db()->warn("failed to retrieve room info from cache: {}", - room_id_.toStdString()); - } -} - -void -RoomSettings::enableEncryption() -{ - const auto room_id = room_id_.toStdString(); - http::client()->enable_encryption( - room_id, [room_id, this](const mtx::responses::EventId &, mtx::http::RequestErr err) { - if (err) { - int status_code = static_cast(err->status_code); - nhlog::net()->warn("failed to enable encryption in room ({}): {} {}", - room_id, - err->matrix_error.error, - status_code); - emit enableEncryptionError( - tr("Failed to enable encryption: %1") - .arg(QString::fromStdString(err->matrix_error.error))); - return; - } - - nhlog::net()->info("enabled encryption on room ({})", room_id); - }); -} - -void -RoomSettings::showEvent(QShowEvent *event) -{ - resetErrorLabel(); - stopLoadingSpinner(); - - QWidget::showEvent(event); -} - -bool -RoomSettings::canChangeJoinRules(const std::string &room_id, const std::string &user_id) const -{ - try { - return cache::hasEnoughPowerLevel({EventType::RoomJoinRules}, room_id, user_id); - } catch (const lmdb::error &e) { - nhlog::db()->warn("lmdb error: {}", e.what()); - } - - return false; -} - -bool -RoomSettings::canChangeNameAndTopic(const std::string &room_id, const std::string &user_id) const -{ - try { - return cache::hasEnoughPowerLevel( - {EventType::RoomName, EventType::RoomTopic}, room_id, user_id); - } catch (const lmdb::error &e) { - nhlog::db()->warn("lmdb error: {}", e.what()); - } - - return false; -} - -bool -RoomSettings::canChangeAvatar(const std::string &room_id, const std::string &user_id) const -{ - try { - return cache::hasEnoughPowerLevel({EventType::RoomAvatar}, room_id, user_id); - } catch (const lmdb::error &e) { - nhlog::db()->warn("lmdb error: {}", e.what()); - } - - return false; -} - -void -RoomSettings::updateAccessRules(const std::string &room_id, - const mtx::events::state::JoinRules &join_rule, - const mtx::events::state::GuestAccess &guest_access) -{ - startLoadingSpinner(); - resetErrorLabel(); - - http::client()->send_state_event( - room_id, - join_rule, - [this, room_id, guest_access](const mtx::responses::EventId &, - mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to send m.room.join_rule: {} {}", - static_cast(err->status_code), - err->matrix_error.error); - emit showErrorMessage(QString::fromStdString(err->matrix_error.error)); - - return; - } - - http::client()->send_state_event( - room_id, - guest_access, - [this](const mtx::responses::EventId &, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to send m.room.guest_access: {} {}", - static_cast(err->status_code), - err->matrix_error.error); - emit showErrorMessage( - QString::fromStdString(err->matrix_error.error)); - - return; - } - - emit accessRulesUpdated(); - }); - }); -} - -void -RoomSettings::stopLoadingSpinner() -{ - if (spinner_) { - spinner_->stop(); - spinner_->hide(); - } -} - -void -RoomSettings::startLoadingSpinner() -{ - if (spinner_) { - spinner_->start(); - spinner_->show(); - } -} - -void -RoomSettings::displayErrorMessage(const QString &msg) -{ - stopLoadingSpinner(); - - errorLabel_->show(); - errorLabel_->setText(msg); -} - -void -RoomSettings::setAvatar() -{ - stopLoadingSpinner(); - - if (avatar_) - avatar_->setImage(QString::fromStdString(info_.avatar_url)); -} - -void -RoomSettings::resetErrorLabel() -{ - if (errorLabel_) { - errorLabel_->hide(); - errorLabel_->clear(); - } -} - -void -RoomSettings::updateAvatar() -{ - const QString picturesFolder = - QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); - const QString fileName = QFileDialog::getOpenFileName( - this, tr("Select an avatar"), picturesFolder, tr("All Files (*)")); - - if (fileName.isEmpty()) - return; - - QMimeDatabase db; - QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent); - - const auto format = mime.name().split("/")[0]; - - QFile file{fileName, this}; - if (format != "image") { - displayErrorMessage(tr("The selected file is not an image")); - return; - } - - if (!file.open(QIODevice::ReadOnly)) { - displayErrorMessage(tr("Error while reading file: %1").arg(file.errorString())); - return; - } - - if (spinner_) { - startLoadingSpinner(); - resetErrorLabel(); - } - - // Events emitted from the http callbacks (different threads) will - // be queued back into the UI thread through this proxy object. - auto proxy = std::make_shared(); - connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayErrorMessage); - connect(proxy.get(), &ThreadProxy::avatarChanged, this, &RoomSettings::setAvatar); - - const auto bin = file.peek(file.size()); - const auto payload = std::string(bin.data(), bin.size()); - const auto dimensions = QImageReader(&file).size(); - - // First we need to create a new mxc URI - // (i.e upload media to the Matrix content repository) for the new avatar. - http::client()->upload( - payload, - mime.name().toStdString(), - QFileInfo(fileName).fileName().toStdString(), - [proxy = std::move(proxy), - dimensions, - payload, - mimetype = mime.name().toStdString(), - size = payload.size(), - room_id = room_id_.toStdString(), - content = std::move(bin)](const mtx::responses::ContentURI &res, - mtx::http::RequestErr err) { - if (err) { - emit proxy->error( - tr("Failed to upload image: %s") - .arg(QString::fromStdString(err->matrix_error.error))); - return; - } - - using namespace mtx::events; - state::Avatar avatar_event; - avatar_event.image_info.w = dimensions.width(); - avatar_event.image_info.h = dimensions.height(); - avatar_event.image_info.mimetype = mimetype; - avatar_event.image_info.size = size; - avatar_event.url = res.content_uri; - - http::client()->send_state_event( - room_id, - avatar_event, - [content = std::move(content), proxy = std::move(proxy)]( - const mtx::responses::EventId &, mtx::http::RequestErr err) { - if (err) { - emit proxy->error( - tr("Failed to upload image: %s") - .arg(QString::fromStdString(err->matrix_error.error))); - return; - } - - emit proxy->avatarChanged(); - }); - }); -} diff --git a/src/dialogs/RoomSettings.h b/src/dialogs/RoomSettings.h deleted file mode 100644 index e0918afd..00000000 --- a/src/dialogs/RoomSettings.h +++ /dev/null @@ -1,150 +0,0 @@ -#pragma once - -#include -#include - -#include - -#include "CacheStructs.h" - -class Avatar; -class FlatButton; -class QPushButton; -class QComboBox; -class QHBoxLayout; -class QShowEvent; -class LoadingIndicator; -class QLayout; -class QPixmap; -class TextField; -class TextField; -class Toggle; -class QLabel; -class QEvent; - -class ClickableFilter : public QObject -{ - Q_OBJECT - -public: - explicit ClickableFilter(QWidget *parent) - : QObject(parent) - {} - -signals: - void clicked(); - -protected: - bool eventFilter(QObject *obj, QEvent *event) override; -}; - -/// Convenience class which connects events emmited from threads -/// outside of main with the UI code. -class ThreadProxy : public QObject -{ - Q_OBJECT - -signals: - void error(const QString &msg); - void avatarChanged(); - void nameEventSent(const QString &); - void topicEventSent(); -}; - -class EditModal : public QWidget -{ - Q_OBJECT - -public: - EditModal(const QString &roomId, QWidget *parent = nullptr); - - void setFields(const QString &roomName, const QString &roomTopic); - -signals: - void nameChanged(const QString &roomName); - -private slots: - void topicEventSent(); - void nameEventSent(const QString &name); - void error(const QString &msg); - - void applyClicked(); - -private: - QString roomId_; - QString initialName_; - QString initialTopic_; - - QLabel *errorField_; - - TextField *nameInput_; - TextField *topicInput_; - - QPushButton *applyBtn_; - QPushButton *cancelBtn_; -}; - -namespace dialogs { - -class RoomSettings : public QFrame -{ - Q_OBJECT -public: - RoomSettings(const QString &room_id, QWidget *parent = nullptr); - -signals: - void enableEncryptionError(const QString &msg); - void showErrorMessage(const QString &msg); - void accessRulesUpdated(); - void notifChanged(int index); - -protected: - void showEvent(QShowEvent *event) override; - -private slots: - //! The file dialog opens so the user can select and upload a new room avatar. - void updateAvatar(); - -private: - //! Whether the user has enough power level to send m.room.join_rules events. - bool canChangeJoinRules(const std::string &room_id, const std::string &user_id) const; - //! Whether the user has enough power level to send m.room.name & m.room.topic events. - bool canChangeNameAndTopic(const std::string &room_id, const std::string &user_id) const; - //! Whether the user has enough power level to send m.room.avatar event. - bool canChangeAvatar(const std::string &room_id, const std::string &user_id) const; - void updateAccessRules(const std::string &room_id, - const mtx::events::state::JoinRules &, - const mtx::events::state::GuestAccess &); - void stopLoadingSpinner(); - void startLoadingSpinner(); - void resetErrorLabel(); - void displayErrorMessage(const QString &msg); - - void setAvatar(); - void setupEditButton(); - //! Retrieve the current room information from cache. - void retrieveRoomInfo(); - void enableEncryption(); - - Avatar *avatar_ = nullptr; - - bool usesEncryption_ = false; - QHBoxLayout *btnLayout_; - - FlatButton *editFieldsBtn_ = nullptr; - - RoomInfo info_; - QString room_id_; - QImage avatarImg_; - - QLabel *roomNameLabel_ = nullptr; - QLabel *errorLabel_ = nullptr; - LoadingIndicator *spinner_ = nullptr; - - QComboBox *notifCombo = nullptr; - QComboBox *accessCombo = nullptr; - Toggle *encryptionToggle_ = nullptr; - Toggle *keyRequestsToggle_ = nullptr; -}; - -} // dialogs diff --git a/src/dialogs/RoomSettingsOld.cpp b/src/dialogs/RoomSettingsOld.cpp new file mode 100644 index 00000000..7eb34c20 --- /dev/null +++ b/src/dialogs/RoomSettingsOld.cpp @@ -0,0 +1,865 @@ +#include "dialogs/RoomSettingsOld.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "Cache.h" +#include "ChatPage.h" +#include "Config.h" +#include "Logging.h" +#include "MatrixClient.h" +#include "Utils.h" +#include "ui/Avatar.h" +#include "ui/FlatButton.h" +#include "ui/LoadingIndicator.h" +#include "ui/Painter.h" +#include "ui/TextField.h" +#include "ui/ToggleButton.h" + +using namespace dialogs; +using namespace mtx::events; + +constexpr int BUTTON_SIZE = 36; +constexpr int BUTTON_RADIUS = BUTTON_SIZE / 2; +constexpr int WIDGET_MARGIN = 20; +constexpr int TOP_WIDGET_MARGIN = 2 * WIDGET_MARGIN; +constexpr int WIDGET_SPACING = 15; +constexpr int TEXT_SPACING = 4; +constexpr int BUTTON_SPACING = 2 * TEXT_SPACING; + +bool +ClickableFilter::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::MouseButtonRelease) { + emit clicked(); + return true; + } + + return QObject::eventFilter(obj, event); +} + +EditModal::EditModal(const QString &roomId, QWidget *parent) + : QWidget(parent) + , roomId_{roomId} +{ + setAutoFillBackground(true); + setAttribute(Qt::WA_DeleteOnClose, true); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + setWindowModality(Qt::WindowModal); + + QFont largeFont; + largeFont.setPointSizeF(largeFont.pointSizeF() * 1.4); + setMinimumWidth(conf::window::minModalWidth); + setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + + auto layout = new QVBoxLayout(this); + + applyBtn_ = new QPushButton(tr("Apply"), this); + cancelBtn_ = new QPushButton(tr("Cancel"), this); + cancelBtn_->setDefault(true); + + auto btnLayout = new QHBoxLayout; + btnLayout->addStretch(1); + btnLayout->setSpacing(15); + btnLayout->addWidget(cancelBtn_); + btnLayout->addWidget(applyBtn_); + + nameInput_ = new TextField(this); + nameInput_->setLabel(tr("Name").toUpper()); + topicInput_ = new TextField(this); + topicInput_->setLabel(tr("Topic").toUpper()); + + errorField_ = new QLabel(this); + errorField_->setWordWrap(true); + errorField_->hide(); + + layout->addWidget(nameInput_); + layout->addWidget(topicInput_); + layout->addLayout(btnLayout, 1); + + auto labelLayout = new QHBoxLayout; + labelLayout->setAlignment(Qt::AlignHCenter); + labelLayout->addWidget(errorField_); + layout->addLayout(labelLayout); + + connect(applyBtn_, &QPushButton::clicked, this, &EditModal::applyClicked); + connect(cancelBtn_, &QPushButton::clicked, this, &EditModal::close); + + auto window = QApplication::activeWindow(); + auto center = window->frameGeometry().center(); + move(center.x() - (width() * 0.5), center.y() - (height() * 0.5)); +} + +void +EditModal::topicEventSent() +{ + errorField_->hide(); + close(); +} + +void +EditModal::nameEventSent(const QString &name) +{ + errorField_->hide(); + emit nameChanged(name); + close(); +} + +void +EditModal::error(const QString &msg) +{ + errorField_->setText(msg); + errorField_->show(); +} + +void +EditModal::applyClicked() +{ + // Check if the values are changed from the originals. + auto newName = nameInput_->text().trimmed(); + auto newTopic = topicInput_->text().trimmed(); + + errorField_->hide(); + + if (newName == initialName_ && newTopic == initialTopic_) { + close(); + return; + } + + using namespace mtx::events; + auto proxy = std::make_shared(); + connect(proxy.get(), &ThreadProxy::topicEventSent, this, &EditModal::topicEventSent); + connect(proxy.get(), &ThreadProxy::nameEventSent, this, &EditModal::nameEventSent); + connect(proxy.get(), &ThreadProxy::error, this, &EditModal::error); + + if (newName != initialName_ && !newName.isEmpty()) { + state::Name body; + body.name = newName.toStdString(); + + http::client()->send_state_event( + roomId_.toStdString(), + body, + [proxy, newName](const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + emit proxy->error( + QString::fromStdString(err->matrix_error.error)); + return; + } + + emit proxy->nameEventSent(newName); + }); + } + + if (newTopic != initialTopic_ && !newTopic.isEmpty()) { + state::Topic body; + body.topic = newTopic.toStdString(); + + http::client()->send_state_event( + roomId_.toStdString(), + body, + [proxy](const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + emit proxy->error( + QString::fromStdString(err->matrix_error.error)); + return; + } + + emit proxy->topicEventSent(); + }); + } +} + +void +EditModal::setFields(const QString &roomName, const QString &roomTopic) +{ + initialName_ = roomName; + initialTopic_ = roomTopic; + + nameInput_->setText(roomName); + topicInput_->setText(roomTopic); +} + +RoomSettingsOld::RoomSettingsOld(const QString &room_id, QWidget *parent) + : QFrame(parent) + , room_id_{std::move(room_id)} +{ + retrieveRoomInfo(); + + setAutoFillBackground(true); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose, true); + + QFont largeFont; + largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5); + + setMinimumWidth(conf::window::minModalWidth); + setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + + auto layout = new QVBoxLayout(this); + layout->setSpacing(WIDGET_SPACING); + layout->setContentsMargins(WIDGET_MARGIN, TOP_WIDGET_MARGIN, WIDGET_MARGIN, WIDGET_MARGIN); + + QFont font; + font.setWeight(QFont::Medium); + auto settingsLabel = new QLabel(tr("Settings").toUpper(), this); + settingsLabel->setFont(font); + + auto infoLabel = new QLabel(tr("Info").toUpper(), this); + infoLabel->setFont(font); + + QFont monospaceFont = QFontDatabase::systemFont(QFontDatabase::FixedFont); + + auto roomIdLabel = new QLabel(room_id, this); + roomIdLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + roomIdLabel->setFont(monospaceFont); + + auto roomIdLayout = new QHBoxLayout; + roomIdLayout->setMargin(0); + roomIdLayout->addWidget(new QLabel(tr("Internal ID"), this), + Qt::AlignBottom | Qt::AlignLeft); + roomIdLayout->addWidget(roomIdLabel, 0, Qt::AlignBottom | Qt::AlignRight); + + auto roomVersionLabel = new QLabel(QString::fromStdString(info_.version), this); + roomVersionLabel->setTextInteractionFlags(Qt::TextSelectableByMouse); + roomVersionLabel->setFont(monospaceFont); + + auto roomVersionLayout = new QHBoxLayout; + roomVersionLayout->setMargin(0); + roomVersionLayout->addWidget(new QLabel(tr("Room Version"), this), + Qt::AlignBottom | Qt::AlignLeft); + roomVersionLayout->addWidget(roomVersionLabel, 0, Qt::AlignBottom | Qt::AlignRight); + + auto notifLabel = new QLabel(tr("Notifications"), this); + notifCombo = new QComboBox(this); + notifCombo->addItem(tr( + "Muted")); //{"conditions":[{"kind":"event_match","key":"room_id","pattern":"!jxlRxnrZCsjpjDubDX:matrix.org"}],"actions":["dont_notify"]} + notifCombo->addItem(tr("Mentions only")); // {"actions":["dont_notify"]} + notifCombo->addItem(tr("All messages")); // delete rule + + connect(this, &RoomSettingsOld::notifChanged, notifCombo, &QComboBox::setCurrentIndex); + http::client()->get_pushrules( + "global", + "override", + room_id_.toStdString(), + [this](const mtx::pushrules::PushRule &rule, mtx::http::RequestErr &err) { + if (err) { + if (err->status_code == boost::beast::http::status::not_found) + http::client()->get_pushrules( + "global", + "room", + room_id_.toStdString(), + [this](const mtx::pushrules::PushRule &rule, + mtx::http::RequestErr &err) { + if (err) { + emit notifChanged(2); // all messages + return; + } + + if (rule.enabled) + emit notifChanged(1); // mentions only + }); + return; + } + + if (rule.enabled) + emit notifChanged(0); // muted + else + emit notifChanged(2); // all messages + }); + + connect(notifCombo, QOverload::of(&QComboBox::activated), [this](int index) { + std::string room_id = room_id_.toStdString(); + if (index == 0) { + // mute room + // delete old rule first, then add new rule + mtx::pushrules::PushRule rule; + rule.actions = {mtx::pushrules::actions::dont_notify{}}; + mtx::pushrules::PushCondition condition; + condition.kind = "event_match"; + condition.key = "room_id"; + condition.pattern = room_id; + rule.conditions = {condition}; + + http::client()->put_pushrules( + "global", + "override", + room_id, + rule, + [room_id](mtx::http::RequestErr &err) { + if (err) + nhlog::net()->error( + "failed to set pushrule for room {}: {} {}", + room_id, + static_cast(err->status_code), + err->matrix_error.error); + http::client()->delete_pushrules( + "global", "room", room_id, [room_id](mtx::http::RequestErr &) { + }); + }); + } else if (index == 1) { + // mentions only + // delete old rule first, then add new rule + mtx::pushrules::PushRule rule; + rule.actions = {mtx::pushrules::actions::dont_notify{}}; + http::client()->put_pushrules( + "global", "room", room_id, rule, [room_id](mtx::http::RequestErr &err) { + if (err) + nhlog::net()->error( + "failed to set pushrule for room {}: {} {}", + room_id, + static_cast(err->status_code), + err->matrix_error.error); + http::client()->delete_pushrules( + "global", + "override", + room_id, + [room_id](mtx::http::RequestErr &) {}); + }); + } else { + // all messages + http::client()->delete_pushrules( + "global", "override", room_id, [room_id](mtx::http::RequestErr &) { + http::client()->delete_pushrules( + "global", "room", room_id, [room_id](mtx::http::RequestErr &) { + }); + }); + } + }); + + auto notifOptionLayout_ = new QHBoxLayout; + notifOptionLayout_->setMargin(0); + notifOptionLayout_->addWidget(notifLabel, Qt::AlignBottom | Qt::AlignLeft); + notifOptionLayout_->addWidget(notifCombo, 0, Qt::AlignBottom | Qt::AlignRight); + + auto accessLabel = new QLabel(tr("Room access"), this); + accessCombo = new QComboBox(this); + accessCombo->addItem(tr("Anyone and guests")); + accessCombo->addItem(tr("Anyone")); + accessCombo->addItem(tr("Invited users")); + accessCombo->setDisabled( + !canChangeJoinRules(room_id_.toStdString(), utils::localUser().toStdString())); + connect(accessCombo, QOverload::of(&QComboBox::activated), [this](int index) { + using namespace mtx::events::state; + + auto guest_access = [](int index) -> state::GuestAccess { + state::GuestAccess event; + + if (index == 0) + event.guest_access = state::AccessState::CanJoin; + else + event.guest_access = state::AccessState::Forbidden; + + return event; + }(index); + + auto join_rule = [](int index) -> state::JoinRules { + state::JoinRules event; + + switch (index) { + case 0: + case 1: + event.join_rule = state::JoinRule::Public; + break; + default: + event.join_rule = state::JoinRule::Invite; + } + + return event; + }(index); + + updateAccessRules(room_id_.toStdString(), join_rule, guest_access); + }); + + if (info_.join_rule == state::JoinRule::Public) { + if (info_.guest_access) { + accessCombo->setCurrentIndex(0); + } else { + accessCombo->setCurrentIndex(1); + } + } else { + accessCombo->setCurrentIndex(2); + } + + auto accessOptionLayout = new QHBoxLayout(); + accessOptionLayout->setMargin(0); + accessOptionLayout->addWidget(accessLabel, Qt::AlignBottom | Qt::AlignLeft); + accessOptionLayout->addWidget(accessCombo, 0, Qt::AlignBottom | Qt::AlignRight); + + auto encryptionLabel = new QLabel(tr("Encryption"), this); + encryptionToggle_ = new Toggle(this); + + auto encryptionOptionLayout = new QHBoxLayout; + encryptionOptionLayout->setMargin(0); + encryptionOptionLayout->addWidget(encryptionLabel, Qt::AlignBottom | Qt::AlignLeft); + encryptionOptionLayout->addWidget(encryptionToggle_, 0, Qt::AlignBottom | Qt::AlignRight); + + auto keyRequestsLabel = new QLabel(tr("Respond to key requests"), this); + keyRequestsLabel->setToolTipDuration(6000); + keyRequestsLabel->setToolTip( + tr("Whether or not the client should respond automatically with the session keys\n" + " upon request. Use with caution, this is a temporary measure to test the\n" + " E2E implementation until device verification is completed.")); + keyRequestsToggle_ = new Toggle(this); + connect(keyRequestsToggle_, &Toggle::toggled, this, [this](bool isOn) { + utils::setKeyRequestsPreference(room_id_, isOn); + }); + + auto keyRequestsLayout = new QHBoxLayout; + keyRequestsLayout->setMargin(0); + keyRequestsLayout->setSpacing(0); + keyRequestsLayout->addWidget(keyRequestsLabel, Qt::AlignBottom | Qt::AlignLeft); + keyRequestsLayout->addWidget(keyRequestsToggle_, 0, Qt::AlignBottom | Qt::AlignRight); + + connect(encryptionToggle_, &Toggle::toggled, this, [this, keyRequestsLabel](bool isOn) { + if (!isOn || usesEncryption_) + return; + + QMessageBox msgBox; + msgBox.setIcon(QMessageBox::Question); + msgBox.setWindowTitle(tr("End-to-End Encryption")); + msgBox.setText(tr( + "Encryption is currently experimental and things might break unexpectedly.
" + "Please take note that it can't be disabled afterwards.")); + msgBox.setStandardButtons(QMessageBox::Ok | QMessageBox::Cancel); + msgBox.setDefaultButton(QMessageBox::Save); + int ret = msgBox.exec(); + + switch (ret) { + case QMessageBox::Ok: { + encryptionToggle_->setState(true); + encryptionToggle_->setEnabled(false); + enableEncryption(); + keyRequestsToggle_->show(); + keyRequestsLabel->show(); + break; + } + default: { + break; + } + } + }); + + // Disable encryption button. + if (usesEncryption_) { + encryptionToggle_->setState(true); + encryptionToggle_->setEnabled(false); + + keyRequestsToggle_->setState(utils::respondsToKeyRequests(room_id_)); + } else { + encryptionToggle_->setState(false); + + keyRequestsLabel->hide(); + keyRequestsToggle_->hide(); + } + + // Hide encryption option for public rooms. + if (!usesEncryption_ && (info_.join_rule == state::JoinRule::Public)) { + encryptionToggle_->hide(); + encryptionLabel->hide(); + + keyRequestsLabel->hide(); + keyRequestsToggle_->hide(); + } + + avatar_ = new Avatar(this, 128); + avatar_->setLetter(utils::firstChar(QString::fromStdString(info_.name))); + if (!info_.avatar_url.empty()) + avatar_->setImage(QString::fromStdString(info_.avatar_url)); + + if (canChangeAvatar(room_id_.toStdString(), utils::localUser().toStdString())) { + auto filter = new ClickableFilter(this); + avatar_->installEventFilter(filter); + avatar_->setCursor(Qt::PointingHandCursor); + connect(filter, &ClickableFilter::clicked, this, &RoomSettingsOld::updateAvatar); + } + + roomNameLabel_ = new QLabel(QString::fromStdString(info_.name), this); + roomNameLabel_->setFont(largeFont); + + auto membersLabel = new QLabel(tr("%n member(s)", "", (int)info_.member_count), this); + + auto textLayout = new QVBoxLayout; + textLayout->addWidget(roomNameLabel_); + textLayout->addWidget(membersLabel); + textLayout->setAlignment(roomNameLabel_, Qt::AlignCenter | Qt::AlignTop); + textLayout->setAlignment(membersLabel, Qt::AlignCenter | Qt::AlignTop); + textLayout->setSpacing(TEXT_SPACING); + textLayout->setMargin(0); + + setupEditButton(); + + errorLabel_ = new QLabel(this); + errorLabel_->setAlignment(Qt::AlignCenter); + errorLabel_->hide(); + + spinner_ = new LoadingIndicator(this); + spinner_->setFixedHeight(30); + spinner_->setFixedWidth(30); + spinner_->hide(); + auto spinnerLayout = new QVBoxLayout; + spinnerLayout->addWidget(spinner_); + spinnerLayout->setAlignment(Qt::AlignCenter); + spinnerLayout->setMargin(0); + spinnerLayout->setSpacing(0); + + auto okBtn = new QPushButton("OK", this); + + auto buttonLayout = new QHBoxLayout(); + buttonLayout->setSpacing(15); + buttonLayout->addStretch(1); + buttonLayout->addWidget(okBtn); + + layout->addWidget(avatar_, Qt::AlignCenter | Qt::AlignTop); + layout->addLayout(textLayout); + layout->addLayout(btnLayout_); + layout->addWidget(settingsLabel, Qt::AlignLeft); + layout->addLayout(notifOptionLayout_); + layout->addLayout(accessOptionLayout); + layout->addLayout(encryptionOptionLayout); + layout->addLayout(keyRequestsLayout); + layout->addWidget(infoLabel, Qt::AlignLeft); + layout->addLayout(roomIdLayout); + layout->addLayout(roomVersionLayout); + layout->addWidget(errorLabel_); + layout->addLayout(buttonLayout); + layout->addLayout(spinnerLayout); + layout->addStretch(1); + + connect(this, &RoomSettingsOld::enableEncryptionError, this, [this](const QString &msg) { + encryptionToggle_->setState(false); + keyRequestsToggle_->setState(false); + keyRequestsToggle_->setEnabled(false); + keyRequestsToggle_->hide(); + + emit ChatPage::instance()->showNotification(msg); + }); + + connect(this, &RoomSettingsOld::showErrorMessage, this, [this](const QString &msg) { + if (!errorLabel_) + return; + + stopLoadingSpinner(); + + errorLabel_->show(); + errorLabel_->setText(msg); + }); + + connect(this, &RoomSettingsOld::accessRulesUpdated, this, [this]() { + stopLoadingSpinner(); + resetErrorLabel(); + }); + + auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); + connect(closeShortcut, &QShortcut::activated, this, &RoomSettingsOld::close); + connect(okBtn, &QPushButton::clicked, this, &RoomSettingsOld::close); +} + +void +RoomSettingsOld::setupEditButton() +{ + btnLayout_ = new QHBoxLayout; + btnLayout_->setSpacing(BUTTON_SPACING); + btnLayout_->setMargin(0); + + if (!canChangeNameAndTopic(room_id_.toStdString(), utils::localUser().toStdString())) + return; + + QIcon editIcon; + editIcon.addFile(":/icons/icons/ui/edit.png"); + editFieldsBtn_ = new FlatButton(this); + editFieldsBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE); + editFieldsBtn_->setCornerRadius(BUTTON_RADIUS); + editFieldsBtn_->setIcon(editIcon); + editFieldsBtn_->setIcon(editIcon); + editFieldsBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS)); + + connect(editFieldsBtn_, &QPushButton::clicked, this, [this]() { + retrieveRoomInfo(); + + auto modal = new EditModal(room_id_, this); + modal->setFields(QString::fromStdString(info_.name), + QString::fromStdString(info_.topic)); + modal->raise(); + modal->show(); + connect(modal, &EditModal::nameChanged, this, [this](const QString &newName) { + if (roomNameLabel_) + roomNameLabel_->setText(newName); + }); + }); + + btnLayout_->addStretch(1); + btnLayout_->addWidget(editFieldsBtn_); + btnLayout_->addStretch(1); +} + +void +RoomSettingsOld::retrieveRoomInfo() +{ + try { + usesEncryption_ = cache::isRoomEncrypted(room_id_.toStdString()); + info_ = cache::singleRoomInfo(room_id_.toStdString()); + setAvatar(); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve room info from cache: {}", + room_id_.toStdString()); + } +} + +void +RoomSettingsOld::enableEncryption() +{ + const auto room_id = room_id_.toStdString(); + http::client()->enable_encryption( + room_id, [room_id, this](const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + int status_code = static_cast(err->status_code); + nhlog::net()->warn("failed to enable encryption in room ({}): {} {}", + room_id, + err->matrix_error.error, + status_code); + emit enableEncryptionError( + tr("Failed to enable encryption: %1") + .arg(QString::fromStdString(err->matrix_error.error))); + return; + } + + nhlog::net()->info("enabled encryption on room ({})", room_id); + }); +} + +void +RoomSettingsOld::showEvent(QShowEvent *event) +{ + resetErrorLabel(); + stopLoadingSpinner(); + + QWidget::showEvent(event); +} + +bool +RoomSettingsOld::canChangeJoinRules(const std::string &room_id, const std::string &user_id) const +{ + try { + return cache::hasEnoughPowerLevel({EventType::RoomJoinRules}, room_id, user_id); + } catch (const lmdb::error &e) { + nhlog::db()->warn("lmdb error: {}", e.what()); + } + + return false; +} + +bool +RoomSettingsOld::canChangeNameAndTopic(const std::string &room_id, const std::string &user_id) const +{ + try { + return cache::hasEnoughPowerLevel( + {EventType::RoomName, EventType::RoomTopic}, room_id, user_id); + } catch (const lmdb::error &e) { + nhlog::db()->warn("lmdb error: {}", e.what()); + } + + return false; +} + +bool +RoomSettingsOld::canChangeAvatar(const std::string &room_id, const std::string &user_id) const +{ + try { + return cache::hasEnoughPowerLevel({EventType::RoomAvatar}, room_id, user_id); + } catch (const lmdb::error &e) { + nhlog::db()->warn("lmdb error: {}", e.what()); + } + + return false; +} + +void +RoomSettingsOld::updateAccessRules(const std::string &room_id, + const mtx::events::state::JoinRules &join_rule, + const mtx::events::state::GuestAccess &guest_access) +{ + startLoadingSpinner(); + resetErrorLabel(); + + http::client()->send_state_event( + room_id, + join_rule, + [this, room_id, guest_access](const mtx::responses::EventId &, + mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send m.room.join_rule: {} {}", + static_cast(err->status_code), + err->matrix_error.error); + emit showErrorMessage(QString::fromStdString(err->matrix_error.error)); + + return; + } + + http::client()->send_state_event( + room_id, + guest_access, + [this](const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to send m.room.guest_access: {} {}", + static_cast(err->status_code), + err->matrix_error.error); + emit showErrorMessage( + QString::fromStdString(err->matrix_error.error)); + + return; + } + + emit accessRulesUpdated(); + }); + }); +} + +void +RoomSettingsOld::stopLoadingSpinner() +{ + if (spinner_) { + spinner_->stop(); + spinner_->hide(); + } +} + +void +RoomSettingsOld::startLoadingSpinner() +{ + if (spinner_) { + spinner_->start(); + spinner_->show(); + } +} + +void +RoomSettingsOld::displayErrorMessage(const QString &msg) +{ + stopLoadingSpinner(); + + errorLabel_->show(); + errorLabel_->setText(msg); +} + +void +RoomSettingsOld::setAvatar() +{ + stopLoadingSpinner(); + + if (avatar_) + avatar_->setImage(QString::fromStdString(info_.avatar_url)); +} + +void +RoomSettingsOld::resetErrorLabel() +{ + if (errorLabel_) { + errorLabel_->hide(); + errorLabel_->clear(); + } +} + +void +RoomSettingsOld::updateAvatar() +{ + const QString picturesFolder = + QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + const QString fileName = QFileDialog::getOpenFileName( + this, tr("Select an avatar"), picturesFolder, tr("All Files (*)")); + + if (fileName.isEmpty()) + return; + + QMimeDatabase db; + QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent); + + const auto format = mime.name().split("/")[0]; + + QFile file{fileName, this}; + if (format != "image") { + displayErrorMessage(tr("The selected file is not an image")); + return; + } + + if (!file.open(QIODevice::ReadOnly)) { + displayErrorMessage(tr("Error while reading file: %1").arg(file.errorString())); + return; + } + + if (spinner_) { + startLoadingSpinner(); + resetErrorLabel(); + } + + // Events emitted from the http callbacks (different threads) will + // be queued back into the UI thread through this proxy object. + auto proxy = std::make_shared(); + connect(proxy.get(), &ThreadProxy::error, this, &RoomSettingsOld::displayErrorMessage); + connect(proxy.get(), &ThreadProxy::avatarChanged, this, &RoomSettingsOld::setAvatar); + + const auto bin = file.peek(file.size()); + const auto payload = std::string(bin.data(), bin.size()); + const auto dimensions = QImageReader(&file).size(); + + // First we need to create a new mxc URI + // (i.e upload media to the Matrix content repository) for the new avatar. + http::client()->upload( + payload, + mime.name().toStdString(), + QFileInfo(fileName).fileName().toStdString(), + [proxy = std::move(proxy), + dimensions, + payload, + mimetype = mime.name().toStdString(), + size = payload.size(), + room_id = room_id_.toStdString(), + content = std::move(bin)](const mtx::responses::ContentURI &res, + mtx::http::RequestErr err) { + if (err) { + emit proxy->error( + tr("Failed to upload image: %s") + .arg(QString::fromStdString(err->matrix_error.error))); + return; + } + + using namespace mtx::events; + state::Avatar avatar_event; + avatar_event.image_info.w = dimensions.width(); + avatar_event.image_info.h = dimensions.height(); + avatar_event.image_info.mimetype = mimetype; + avatar_event.image_info.size = size; + avatar_event.url = res.content_uri; + + http::client()->send_state_event( + room_id, + avatar_event, + [content = std::move(content), proxy = std::move(proxy)]( + const mtx::responses::EventId &, mtx::http::RequestErr err) { + if (err) { + emit proxy->error( + tr("Failed to upload image: %s") + .arg(QString::fromStdString(err->matrix_error.error))); + return; + } + + emit proxy->avatarChanged(); + }); + }); +} diff --git a/src/dialogs/RoomSettingsOld.h b/src/dialogs/RoomSettingsOld.h new file mode 100644 index 00000000..e517676a --- /dev/null +++ b/src/dialogs/RoomSettingsOld.h @@ -0,0 +1,150 @@ +#pragma once + +#include +#include + +#include + +#include "CacheStructs.h" + +class Avatar; +class FlatButton; +class QPushButton; +class QComboBox; +class QHBoxLayout; +class QShowEvent; +class LoadingIndicator; +class QLayout; +class QPixmap; +class TextField; +class TextField; +class Toggle; +class QLabel; +class QEvent; + +class ClickableFilter : public QObject +{ + Q_OBJECT + +public: + explicit ClickableFilter(QWidget *parent) + : QObject(parent) + {} + +signals: + void clicked(); + +protected: + bool eventFilter(QObject *obj, QEvent *event) override; +}; + +/// Convenience class which connects events emmited from threads +/// outside of main with the UI code. +class ThreadProxy : public QObject +{ + Q_OBJECT + +signals: + void error(const QString &msg); + void avatarChanged(); + void nameEventSent(const QString &); + void topicEventSent(); +}; + +class EditModal : public QWidget +{ + Q_OBJECT + +public: + EditModal(const QString &roomId, QWidget *parent = nullptr); + + void setFields(const QString &roomName, const QString &roomTopic); + +signals: + void nameChanged(const QString &roomName); + +private slots: + void topicEventSent(); + void nameEventSent(const QString &name); + void error(const QString &msg); + + void applyClicked(); + +private: + QString roomId_; + QString initialName_; + QString initialTopic_; + + QLabel *errorField_; + + TextField *nameInput_; + TextField *topicInput_; + + QPushButton *applyBtn_; + QPushButton *cancelBtn_; +}; + +namespace dialogs { + +class RoomSettingsOld : public QFrame +{ + Q_OBJECT +public: + RoomSettingsOld(const QString &room_id, QWidget *parent = nullptr); + +signals: + void enableEncryptionError(const QString &msg); + void showErrorMessage(const QString &msg); + void accessRulesUpdated(); + void notifChanged(int index); + +protected: + void showEvent(QShowEvent *event) override; + +private slots: + //! The file dialog opens so the user can select and upload a new room avatar. + void updateAvatar(); + +private: + //! Whether the user has enough power level to send m.room.join_rules events. + bool canChangeJoinRules(const std::string &room_id, const std::string &user_id) const; + //! Whether the user has enough power level to send m.room.name & m.room.topic events. + bool canChangeNameAndTopic(const std::string &room_id, const std::string &user_id) const; + //! Whether the user has enough power level to send m.room.avatar event. + bool canChangeAvatar(const std::string &room_id, const std::string &user_id) const; + void updateAccessRules(const std::string &room_id, + const mtx::events::state::JoinRules &, + const mtx::events::state::GuestAccess &); + void stopLoadingSpinner(); + void startLoadingSpinner(); + void resetErrorLabel(); + void displayErrorMessage(const QString &msg); + + void setAvatar(); + void setupEditButton(); + //! Retrieve the current room information from cache. + void retrieveRoomInfo(); + void enableEncryption(); + + Avatar *avatar_ = nullptr; + + bool usesEncryption_ = false; + QHBoxLayout *btnLayout_; + + FlatButton *editFieldsBtn_ = nullptr; + + RoomInfo info_; + QString room_id_; + QImage avatarImg_; + + QLabel *roomNameLabel_ = nullptr; + QLabel *errorLabel_ = nullptr; + LoadingIndicator *spinner_ = nullptr; + + QComboBox *notifCombo = nullptr; + QComboBox *accessCombo = nullptr; + Toggle *encryptionToggle_ = nullptr; + Toggle *keyRequestsToggle_ = nullptr; +}; + +} // dialogs diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index d7d06386..7c1922d7 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -393,7 +393,9 @@ void TimelineViewManager::openRoomSettings() { MainWindow::instance()->openRoomSettings(timeline_->roomId()); - emit openRoomSettingsDialog(); + + RoomSettings *roomSettings = new RoomSettings(timeline_->roomId(), this); + emit openRoomSettingsDialog(roomSettings); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 841e0bcb..dca133ce 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -18,7 +18,8 @@ #include "WebRTCSession.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" -#include "dialogs/RoomSettings.h" +#include "dialogs/RoomSettingsOld.h" +#include "ui/RoomSettings.h" class MxcImageProvider; class BlurhashProvider; @@ -88,7 +89,7 @@ signals: void showRoomList(); void narrowViewChanged(); void focusChanged(); - void openRoomSettingsDialog(); + void openRoomSettingsDialog(RoomSettings *roomSettings); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp new file mode 100644 index 00000000..e8317024 --- /dev/null +++ b/src/ui/RoomSettings.cpp @@ -0,0 +1,33 @@ +#include "RoomSettings.h" + +#include +#include + +#include "Cache.h" +#include "Logging.h" + +RoomSettings::RoomSettings(QString roomid, QObject *parent) + : roomid_{std::move(roomid)} + , QObject(parent) +{ + retrieveRoomInfo(); +} + +QString +RoomSettings::roomName() const +{ + return QString(info_.name.c_str()); +} + +void +RoomSettings::retrieveRoomInfo() +{ + try { + usesEncryption_ = cache::isRoomEncrypted(roomid_.toStdString()); + info_ = cache::singleRoomInfo(roomid_.toStdString()); + //setAvatar(); + } catch (const lmdb::error &) { + nhlog::db()->warn("failed to retrieve room info from cache: {}", + roomid_.toStdString()); + } +} \ No newline at end of file diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h new file mode 100644 index 00000000..98e64b74 --- /dev/null +++ b/src/ui/RoomSettings.h @@ -0,0 +1,25 @@ +#pragma once + +#include +#include + +#include "CacheStructs.h" + +class RoomSettings : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString roomName READ roomName CONSTANT) + +public: + RoomSettings(QString roomid, QObject *parent = nullptr); + + QString roomName() const; + +private: + void retrieveRoomInfo(); + +private: + QString roomid_; + bool usesEncryption_ = false; + RoomInfo info_; +}; \ No newline at end of file -- cgit 1.5.1