From 37679ac57ec79d112a76bd8afb0dfea7e433da80 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Tue, 9 Feb 2021 23:11:39 +0530 Subject: [PATCH 01/45] added room settings qml --- resources/qml/RoomSettings.qml | 157 +++++++++++++++++++++++++++ resources/qml/TimelineView.qml | 16 +++ resources/res.qrc | 1 + src/timeline/TimelineViewManager.cpp | 3 +- src/timeline/TimelineViewManager.h | 4 +- 5 files changed, 179 insertions(+), 2 deletions(-) create mode 100644 resources/qml/RoomSettings.qml diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml new file mode 100644 index 00000000..106c9119 --- /dev/null +++ b/resources/qml/RoomSettings.qml @@ -0,0 +1,157 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.3 +import QtQuick.Layouts 1.2 +import QtQuick.Window 2.3 +import im.nheko 1.0 + +ApplicationWindow { + id: roomSettingsDialog + + property var roomSettings + + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + y: MainWindow.y + (MainWindow.height / 2) - (height / 2) + height: 600 + width: 420 + minimumHeight: 420 + palette: colors + color: colors.window + title: "Room Settings" + modality: Qt.Modal + + Shortcut { + sequence: StandardKey.Cancel + onActivated: roomSettingsDialog.close() + } + + ColumnLayout { + id: contentLayout + + anchors.fill: parent + anchors.margins: 10 + spacing: 10 + + Avatar { + url: "" + height: 130 + width: 130 + displayName: "" + userid: "" + Layout.alignment: Qt.AlignHCenter + } + + ColumnLayout { + Layout.alignment: Qt.AlignHCenter + + MatrixText { + text: "room name" + font.pixelSize: 24 + Layout.alignment: Qt.AlignHCenter + } + + MatrixText { + text: "1 member" + Layout.alignment: Qt.AlignHCenter + } + } + + ImageButton { + Layout.alignment: Qt.AlignHCenter + image: ":/icons/icons/ui/edit.png" + } + + MatrixText { + text: "SETTINGS" + } + + RowLayout { + MatrixText { + text: "Notifications" + } + + Item { + Layout.fillWidth: true + } + + ComboBox { + model: [ "Muted", "Mentions only", "All messages" ] + } + } + + RowLayout { + MatrixText { + text: "Room access" + } + + ComboBox { + Layout.fillWidth: true + model: [ "Anyone and guests", "Anyone", "Invited users" ] + } + } + + RowLayout { + MatrixText { + text: "Encryption" + } + + Item { + Layout.fillWidth: true + } + + Switch { + } + } + + RowLayout { + MatrixText { + text: "Respond to key requests" + } + + Item { + Layout.fillWidth: true + } + + Switch { + } + } + + MatrixText { + text: "INFO" + } + + RowLayout { + MatrixText { + text: "Internal ID" + } + + Item { + Layout.fillWidth: true + } + + MatrixText { + text: "asdajdhasjkdhaskjdhasjdks" + font.pixelSize: 12 + } + } + + RowLayout { + MatrixText { + text: "Room Version" + } + + Item { + Layout.fillWidth: true + } + + MatrixText { + text: "6" + font.pixelSize: 12 + } + } + + Button { + Layout.alignment: Qt.AlignRight + text: "Ok" + } + } +} \ No newline at end of file diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index c03e8d31..c6c1e2b2 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -52,6 +52,14 @@ Page { } + Component { + id: roomSettingsComponent + + RoomSettings { + } + + } + Component { id: mobileCallInviteDialog @@ -166,6 +174,14 @@ Page { } } + Connections { + target: TimelineManager + onOpenRoomSettingsDialog: { + var roomSettings = roomSettingsComponent.createObject(timelineRoot); + roomSettings.show(); + } + } + Connections { target: CallManager onNewInviteState: { diff --git a/resources/res.qrc b/resources/res.qrc index 308d81a6..24b41179 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -139,6 +139,7 @@ qml/TimelineRow.qml qml/TopBar.qml qml/TypingIndicator.qml + qml/RoomSettings.qml qml/emoji/EmojiButton.qml qml/emoji/EmojiPicker.qml qml/UserProfile.qml diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 93451976..d7d06386 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -390,9 +390,10 @@ TimelineViewManager::openLeaveRoomDialog() const MainWindow::instance()->openLeaveRoomDialog(timeline_->roomId()); } void -TimelineViewManager::openRoomSettings() const +TimelineViewManager::openRoomSettings() { MainWindow::instance()->openRoomSettings(timeline_->roomId()); + emit openRoomSettingsDialog(); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 74128865..841e0bcb 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -18,6 +18,7 @@ #include "WebRTCSession.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" +#include "dialogs/RoomSettings.h" class MxcImageProvider; class BlurhashProvider; @@ -69,7 +70,7 @@ public: Q_INVOKABLE void openInviteUsersDialog(); Q_INVOKABLE void openMemberListDialog() const; Q_INVOKABLE void openLeaveRoomDialog() const; - Q_INVOKABLE void openRoomSettings() const; + Q_INVOKABLE void openRoomSettings(); Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); void verifyUser(QString userid); @@ -87,6 +88,7 @@ signals: void showRoomList(); void narrowViewChanged(); void focusChanged(); + void openRoomSettingsDialog(); public slots: void updateReadReceipts(const QString &room_id, const std::vector &event_ids); From b70f37194fd968950c920b1c39388154aa46cdc1 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Wed, 10 Feb 2021 21:22:42 +0530 Subject: [PATCH 02/45] ui almost looks the same, midway between transition from old room settings to new room settings --- CMakeLists.txt | 6 ++- resources/qml/RoomSettings.qml | 2 +- resources/qml/TimelineView.qml | 4 +- resources/qml/ToggleButton.qml | 10 ++++ src/MainWindow.cpp | 4 +- src/MainWindow.h | 2 +- .../{RoomSettings.cpp => RoomSettingsOld.cpp} | 50 +++++++++---------- .../{RoomSettings.h => RoomSettingsOld.h} | 4 +- src/timeline/TimelineViewManager.cpp | 4 +- src/timeline/TimelineViewManager.h | 5 +- src/ui/RoomSettings.cpp | 33 ++++++++++++ src/ui/RoomSettings.h | 25 ++++++++++ 12 files changed, 112 insertions(+), 37 deletions(-) create mode 100644 resources/qml/ToggleButton.qml rename src/dialogs/{RoomSettings.cpp => RoomSettingsOld.cpp} (96%) rename src/dialogs/{RoomSettings.h => RoomSettingsOld.h} (97%) create mode 100644 src/ui/RoomSettings.cpp create mode 100644 src/ui/RoomSettings.h diff --git a/CMakeLists.txt b/CMakeLists.txt index c9e29998..505a59f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,7 +257,7 @@ set(SRC_FILES src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp src/dialogs/ReadReceipts.cpp - src/dialogs/RoomSettings.cpp + src/dialogs/RoomSettingsOld.cpp # Emoji src/emoji/EmojiModel.cpp @@ -294,6 +294,7 @@ set(SRC_FILES src/ui/ThemeManager.cpp src/ui/ToggleButton.cpp src/ui/UserProfile.cpp + src/ui/RoomSettings.cpp src/AvatarProvider.cpp src/BlurhashProvider.cpp @@ -471,7 +472,7 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/RawMessage.h src/dialogs/ReCaptcha.h src/dialogs/ReadReceipts.h - src/dialogs/RoomSettings.h + src/dialogs/RoomSettingsOld.h # Emoji src/emoji/EmojiModel.h @@ -506,6 +507,7 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/Theme.h src/ui/ThemeManager.h src/ui/UserProfile.h + src/ui/RoomSettings.h src/notifications/Manager.h diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 106c9119..d6f3fe7b 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -16,7 +16,7 @@ ApplicationWindow { minimumHeight: 420 palette: colors color: colors.window - title: "Room Settings" + title: roomSettings.roomName modality: Qt.Modal Shortcut { diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index c6c1e2b2..6b34f2ab 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -177,7 +177,9 @@ Page { Connections { target: TimelineManager onOpenRoomSettingsDialog: { - var roomSettings = roomSettingsComponent.createObject(timelineRoot); + var roomSettings = roomSettingsComponent.createObject(timelineRoot, { + "roomSettings": roomSettings + }); roomSettings.show(); } } diff --git a/resources/qml/ToggleButton.qml b/resources/qml/ToggleButton.qml new file mode 100644 index 00000000..584fc693 --- /dev/null +++ b/resources/qml/ToggleButton.qml @@ -0,0 +1,10 @@ +import QtQuick 2.5 +import QtQuick.Controls 2.3 +import im.nheko 1.0 + +Switch { + property color activeColor + property color disabledColor + property color inactiveColor + property color trackColor +} \ No newline at end of file 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/RoomSettingsOld.cpp similarity index 96% rename from src/dialogs/RoomSettings.cpp rename to src/dialogs/RoomSettingsOld.cpp index bd3cc26f..7eb34c20 100644 --- a/src/dialogs/RoomSettings.cpp +++ b/src/dialogs/RoomSettingsOld.cpp @@ -1,4 +1,4 @@ -#include "dialogs/RoomSettings.h" +#include "dialogs/RoomSettingsOld.h" #include #include #include @@ -195,7 +195,7 @@ EditModal::setFields(const QString &roomName, const QString &roomTopic) topicInput_->setText(roomTopic); } -RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) +RoomSettingsOld::RoomSettingsOld(const QString &room_id, QWidget *parent) : QFrame(parent) , room_id_{std::move(room_id)} { @@ -253,7 +253,7 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) notifCombo->addItem(tr("Mentions only")); // {"actions":["dont_notify"]} notifCombo->addItem(tr("All messages")); // delete rule - connect(this, &RoomSettings::notifChanged, notifCombo, &QComboBox::setCurrentIndex); + connect(this, &RoomSettingsOld::notifChanged, notifCombo, &QComboBox::setCurrentIndex); http::client()->get_pushrules( "global", "override", @@ -487,7 +487,7 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) auto filter = new ClickableFilter(this); avatar_->installEventFilter(filter); avatar_->setCursor(Qt::PointingHandCursor); - connect(filter, &ClickableFilter::clicked, this, &RoomSettings::updateAvatar); + connect(filter, &ClickableFilter::clicked, this, &RoomSettingsOld::updateAvatar); } roomNameLabel_ = new QLabel(QString::fromStdString(info_.name), this); @@ -542,7 +542,7 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) layout->addLayout(spinnerLayout); layout->addStretch(1); - connect(this, &RoomSettings::enableEncryptionError, this, [this](const QString &msg) { + connect(this, &RoomSettingsOld::enableEncryptionError, this, [this](const QString &msg) { encryptionToggle_->setState(false); keyRequestsToggle_->setState(false); keyRequestsToggle_->setEnabled(false); @@ -551,7 +551,7 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) emit ChatPage::instance()->showNotification(msg); }); - connect(this, &RoomSettings::showErrorMessage, this, [this](const QString &msg) { + connect(this, &RoomSettingsOld::showErrorMessage, this, [this](const QString &msg) { if (!errorLabel_) return; @@ -561,18 +561,18 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent) errorLabel_->setText(msg); }); - connect(this, &RoomSettings::accessRulesUpdated, this, [this]() { + connect(this, &RoomSettingsOld::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); + connect(closeShortcut, &QShortcut::activated, this, &RoomSettingsOld::close); + connect(okBtn, &QPushButton::clicked, this, &RoomSettingsOld::close); } void -RoomSettings::setupEditButton() +RoomSettingsOld::setupEditButton() { btnLayout_ = new QHBoxLayout; btnLayout_->setSpacing(BUTTON_SPACING); @@ -610,7 +610,7 @@ RoomSettings::setupEditButton() } void -RoomSettings::retrieveRoomInfo() +RoomSettingsOld::retrieveRoomInfo() { try { usesEncryption_ = cache::isRoomEncrypted(room_id_.toStdString()); @@ -623,7 +623,7 @@ RoomSettings::retrieveRoomInfo() } void -RoomSettings::enableEncryption() +RoomSettingsOld::enableEncryption() { const auto room_id = room_id_.toStdString(); http::client()->enable_encryption( @@ -645,7 +645,7 @@ RoomSettings::enableEncryption() } void -RoomSettings::showEvent(QShowEvent *event) +RoomSettingsOld::showEvent(QShowEvent *event) { resetErrorLabel(); stopLoadingSpinner(); @@ -654,7 +654,7 @@ RoomSettings::showEvent(QShowEvent *event) } bool -RoomSettings::canChangeJoinRules(const std::string &room_id, const std::string &user_id) const +RoomSettingsOld::canChangeJoinRules(const std::string &room_id, const std::string &user_id) const { try { return cache::hasEnoughPowerLevel({EventType::RoomJoinRules}, room_id, user_id); @@ -666,7 +666,7 @@ RoomSettings::canChangeJoinRules(const std::string &room_id, const std::string & } bool -RoomSettings::canChangeNameAndTopic(const std::string &room_id, const std::string &user_id) const +RoomSettingsOld::canChangeNameAndTopic(const std::string &room_id, const std::string &user_id) const { try { return cache::hasEnoughPowerLevel( @@ -679,7 +679,7 @@ RoomSettings::canChangeNameAndTopic(const std::string &room_id, const std::strin } bool -RoomSettings::canChangeAvatar(const std::string &room_id, const std::string &user_id) const +RoomSettingsOld::canChangeAvatar(const std::string &room_id, const std::string &user_id) const { try { return cache::hasEnoughPowerLevel({EventType::RoomAvatar}, room_id, user_id); @@ -691,7 +691,7 @@ RoomSettings::canChangeAvatar(const std::string &room_id, const std::string &use } void -RoomSettings::updateAccessRules(const std::string &room_id, +RoomSettingsOld::updateAccessRules(const std::string &room_id, const mtx::events::state::JoinRules &join_rule, const mtx::events::state::GuestAccess &guest_access) { @@ -732,7 +732,7 @@ RoomSettings::updateAccessRules(const std::string &room_id, } void -RoomSettings::stopLoadingSpinner() +RoomSettingsOld::stopLoadingSpinner() { if (spinner_) { spinner_->stop(); @@ -741,7 +741,7 @@ RoomSettings::stopLoadingSpinner() } void -RoomSettings::startLoadingSpinner() +RoomSettingsOld::startLoadingSpinner() { if (spinner_) { spinner_->start(); @@ -750,7 +750,7 @@ RoomSettings::startLoadingSpinner() } void -RoomSettings::displayErrorMessage(const QString &msg) +RoomSettingsOld::displayErrorMessage(const QString &msg) { stopLoadingSpinner(); @@ -759,7 +759,7 @@ RoomSettings::displayErrorMessage(const QString &msg) } void -RoomSettings::setAvatar() +RoomSettingsOld::setAvatar() { stopLoadingSpinner(); @@ -768,7 +768,7 @@ RoomSettings::setAvatar() } void -RoomSettings::resetErrorLabel() +RoomSettingsOld::resetErrorLabel() { if (errorLabel_) { errorLabel_->hide(); @@ -777,7 +777,7 @@ RoomSettings::resetErrorLabel() } void -RoomSettings::updateAvatar() +RoomSettingsOld::updateAvatar() { const QString picturesFolder = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); @@ -811,8 +811,8 @@ RoomSettings::updateAvatar() // 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); + 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()); diff --git a/src/dialogs/RoomSettings.h b/src/dialogs/RoomSettingsOld.h similarity index 97% rename from src/dialogs/RoomSettings.h rename to src/dialogs/RoomSettingsOld.h index e0918afd..e517676a 100644 --- a/src/dialogs/RoomSettings.h +++ b/src/dialogs/RoomSettingsOld.h @@ -86,11 +86,11 @@ private: namespace dialogs { -class RoomSettings : public QFrame +class RoomSettingsOld : public QFrame { Q_OBJECT public: - RoomSettings(const QString &room_id, QWidget *parent = nullptr); + RoomSettingsOld(const QString &room_id, QWidget *parent = nullptr); signals: void enableEncryptionError(const QString &msg); 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 From 7401bd13b272bbe9a0415c2e523f8ec0fa7f3e11 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Thu, 11 Feb 2021 19:54:09 +0530 Subject: [PATCH 03/45] added notifications and encryption for the new roomsettings --- resources/qml/RoomSettings.qml | 58 +++++- resources/qml/TimelineView.qml | 2 +- src/timeline/TimelineViewManager.cpp | 10 +- src/timeline/TimelineViewManager.h | 2 +- src/ui/RoomSettings.cpp | 257 ++++++++++++++++++++++++++- src/ui/RoomSettings.h | 29 +++ 6 files changed, 352 insertions(+), 6 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index d6f3fe7b..4b03e08b 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -2,6 +2,7 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.3 +import QtQuick.Dialogs 1.2 import im.nheko 1.0 ApplicationWindow { @@ -17,7 +18,8 @@ ApplicationWindow { palette: colors color: colors.window title: roomSettings.roomName - modality: Qt.Modal + modality: Qt.WindowModal + flags: Qt.WindowStaysOnTopHint Shortcut { sequence: StandardKey.Cancel @@ -75,6 +77,10 @@ ApplicationWindow { ComboBox { model: [ "Muted", "Mentions only", "All messages" ] + currentIndex: roomSettings.notifications + onActivated: { + roomSettings.changeNotifications(index) + } } } @@ -85,7 +91,12 @@ ApplicationWindow { ComboBox { Layout.fillWidth: true + enabled: roomSettings.canChangeJoinRules model: [ "Anyone and guests", "Anyone", "Invited users" ] + currentIndex: roomSettings.accessJoinRules + onActivated: { + roomSettings.changeAccessRules(index) + } } } @@ -99,10 +110,46 @@ ApplicationWindow { } Switch { + id: encryptionSwitch + + checked: roomSettings.isEncryptionEnabled + onToggled: { + if(roomSettings.isEncryptionEnabled) { + checked=true; + return; + } + + confirmEncryptionDialog.open(); + } + } + + MessageDialog { + id: confirmEncryptionDialog + title: qsTr("End-to-End Encryption") + text: qsTr("Encryption is currently experimental and things might break unexpectedly.
+ Please take note that it can't be disabled afterwards.") + modality: Qt.WindowModal + icon: StandardIcon.Question + + onAccepted: { + if(roomSettings.isEncryptionEnabled) { + return; + } + + roomSettings.enableEncryption(); + } + + onRejected: { + encryptionSwitch.checked = false + } + + standardButtons: Dialog.Ok | Dialog.Cancel } } RowLayout { + visible: roomSettings.isEncryptionEnabled + MatrixText { text: "Respond to key requests" } @@ -112,6 +159,15 @@ ApplicationWindow { } Switch { + ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the + E2E implementation until device verification is completed.") + + checked: roomSettings.respondsToKeyRequests + + onToggled: { + roomSettings.changeKeyRequestsPreference(checked) + } } } diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 6b34f2ab..0c32effd 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -178,7 +178,7 @@ Page { target: TimelineManager onOpenRoomSettingsDialog: { var roomSettings = roomSettingsComponent.createObject(timelineRoot, { - "roomSettings": roomSettings + "roomSettings": settings }); roomSettings.show(); } diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 7c1922d7..4edc3369 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -126,6 +126,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par 0, "UserProfileModel", "UserProfile needs to be instantiated on the C++ side"); + qmlRegisterUncreatableType( + "im.nheko", + 1, + 0, + "RoomSettingsModel", + "Room Settings needs to be instantiated on the C++ side"); static auto self = this; qmlRegisterSingletonType( @@ -394,8 +400,8 @@ TimelineViewManager::openRoomSettings() { MainWindow::instance()->openRoomSettings(timeline_->roomId()); - RoomSettings *roomSettings = new RoomSettings(timeline_->roomId(), this); - emit openRoomSettingsDialog(roomSettings); + RoomSettings *settings = new RoomSettings(timeline_->roomId(), this); + emit openRoomSettingsDialog(settings); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index dca133ce..10708033 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -89,7 +89,7 @@ signals: void showRoomList(); void narrowViewChanged(); void focusChanged(); - void openRoomSettingsDialog(RoomSettings *roomSettings); + void openRoomSettingsDialog(RoomSettings *settings); 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 index e8317024..785452d0 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -5,12 +5,65 @@ #include "Cache.h" #include "Logging.h" +#include "MatrixClient.h" +#include "Utils.h" + +using namespace mtx::events; RoomSettings::RoomSettings(QString roomid, QObject *parent) : roomid_{std::move(roomid)} , QObject(parent) { retrieveRoomInfo(); + + // get room setting notifications + http::client()->get_pushrules( + "global", + "override", + roomid_.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", + roomid_.toStdString(), + [this](const mtx::pushrules::PushRule &rule, + mtx::http::RequestErr &err) { + if (err) { + notifications_ = 2; // all messages + emit notificationsChanged(); + return; + } + + if (rule.enabled) { + notifications_ = 1; // mentions only + emit notificationsChanged(); + } + }); + return; + } + + if (rule.enabled) { + notifications_ = 0; // muted + emit notificationsChanged(); + } else { + notifications_ = 2; // all messages + emit notificationsChanged(); + } + }); + + // access rules + if (info_.join_rule == state::JoinRule::Public) { + if (info_.guest_access) { + accessRules_ = 0; + } else { + accessRules_ = 1; + } + } else { + accessRules_ = 2; + } + emit accessJoinRulesChanged(); } QString @@ -25,9 +78,211 @@ RoomSettings::retrieveRoomInfo() try { usesEncryption_ = cache::isRoomEncrypted(roomid_.toStdString()); info_ = cache::singleRoomInfo(roomid_.toStdString()); - //setAvatar(); + // setAvatar(); } catch (const lmdb::error &) { nhlog::db()->warn("failed to retrieve room info from cache: {}", roomid_.toStdString()); } +} + +int +RoomSettings::notifications() +{ + return notifications_; +} + +int +RoomSettings::accessJoinRules() +{ + return accessRules_; +} + +bool +RoomSettings::respondsToKeyRequests() +{ + return usesEncryption_ && utils::respondsToKeyRequests(roomid_); +} + +void +RoomSettings::changeKeyRequestsPreference(bool isOn) +{ + utils::setKeyRequestsPreference(roomid_, isOn); + emit keyRequestsChanged(); +} + +void +RoomSettings::enableEncryption() +{ + if (usesEncryption_) + return; + + const auto room_id = roomid_.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))); + usesEncryption_ = false; + emit encryptionChanged(); + return; + } + + nhlog::net()->info("enabled encryption on room ({})", room_id); + }); + + usesEncryption_ = true; + emit encryptionChanged(); +} + +bool +RoomSettings::canChangeJoinRules() const +{ + try { + return cache::hasEnoughPowerLevel({EventType::RoomJoinRules}, + roomid_.toStdString(), + utils::localUser().toStdString()); + } catch (const lmdb::error &e) { + nhlog::db()->warn("lmdb error: {}", e.what()); + } + + return false; +} + +bool +RoomSettings::isEncryptionEnabled() const +{ + return usesEncryption_; +} + +void +RoomSettings::changeNotifications(int currentIndex) +{ + notifications_ = currentIndex; + + std::string room_id = roomid_.toStdString(); + if (notifications_ == 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 (notifications_ == 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 &) {}); + }); + } +} + +void +RoomSettings::changeAccessRules(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(roomid_.toStdString(), join_rule, guest_access); +} + +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 signal that stops loading spinner and reset error label + }); + }); } \ No newline at end of file diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 98e64b74..098e27ba 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -3,23 +3,52 @@ #include #include +#include + #include "CacheStructs.h" class RoomSettings : public QObject { Q_OBJECT Q_PROPERTY(QString roomName READ roomName CONSTANT) + Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged) + Q_PROPERTY(int accessJoinRules READ accessJoinRules NOTIFY accessJoinRulesChanged) + Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT) + Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged) + Q_PROPERTY(bool respondsToKeyRequests READ respondsToKeyRequests NOTIFY keyRequestsChanged) public: RoomSettings(QString roomid, QObject *parent = nullptr); QString roomName() const; + int notifications(); + int accessJoinRules(); + bool respondsToKeyRequests(); + //! Whether the user has enough power level to send m.room.join_rules events. + bool canChangeJoinRules() const; + bool isEncryptionEnabled() const; + + Q_INVOKABLE void changeNotifications(int currentIndex); + Q_INVOKABLE void changeAccessRules(int index); + Q_INVOKABLE void changeKeyRequestsPreference(bool isOn); + Q_INVOKABLE void enableEncryption(); + +signals: + void notificationsChanged(); + void accessJoinRulesChanged(); + void keyRequestsChanged(); + void encryptionChanged(); private: void retrieveRoomInfo(); + void updateAccessRules(const std::string &room_id, + const mtx::events::state::JoinRules &, + const mtx::events::state::GuestAccess &); private: QString roomid_; bool usesEncryption_ = false; RoomInfo info_; + int notifications_ = 0; + int accessRules_ = 0; }; \ No newline at end of file From 473b14ed0f8053348000db370451cfff3ce3ac13 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Thu, 11 Feb 2021 21:23:33 +0530 Subject: [PATCH 04/45] added roomversion, roomid etc --- resources/qml/RoomSettings.qml | 9 ++++----- src/ui/RoomSettings.cpp | 18 ++++++++++++++++++ src/ui/RoomSettings.h | 6 ++++++ 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 4b03e08b..ee824ba0 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -17,7 +17,6 @@ ApplicationWindow { minimumHeight: 420 palette: colors color: colors.window - title: roomSettings.roomName modality: Qt.WindowModal flags: Qt.WindowStaysOnTopHint @@ -46,13 +45,13 @@ ApplicationWindow { Layout.alignment: Qt.AlignHCenter MatrixText { - text: "room name" + text: roomSettings.roomName font.pixelSize: 24 Layout.alignment: Qt.AlignHCenter } MatrixText { - text: "1 member" + text: "%1 member(s)".arg(roomSettings.memberCount) Layout.alignment: Qt.AlignHCenter } } @@ -185,7 +184,7 @@ ApplicationWindow { } MatrixText { - text: "asdajdhasjkdhaskjdhasjdks" + text: roomSettings.roomId font.pixelSize: 12 } } @@ -200,7 +199,7 @@ ApplicationWindow { } MatrixText { - text: "6" + text: roomSettings.roomVersion font.pixelSize: 12 } } diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index 785452d0..49e48e40 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -72,6 +72,24 @@ RoomSettings::roomName() const return QString(info_.name.c_str()); } +QString +RoomSettings::roomId() const +{ + return roomid_; +} + +QString +RoomSettings::roomVersion() const +{ + return QString::fromStdString(info_.version); +} + +int +RoomSettings::memberCount() const +{ + return info_.member_count; +} + void RoomSettings::retrieveRoomInfo() { diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 098e27ba..f5cc043c 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -11,6 +11,9 @@ class RoomSettings : public QObject { Q_OBJECT Q_PROPERTY(QString roomName READ roomName CONSTANT) + Q_PROPERTY(QString roomId READ roomId CONSTANT) + Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT) + Q_PROPERTY(int memberCount READ memberCount CONSTANT) Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged) Q_PROPERTY(int accessJoinRules READ accessJoinRules NOTIFY accessJoinRulesChanged) Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT) @@ -21,6 +24,9 @@ public: RoomSettings(QString roomid, QObject *parent = nullptr); QString roomName() const; + QString roomId() const; + QString roomVersion() const; + int memberCount() const; int notifications(); int accessJoinRules(); bool respondsToKeyRequests(); From a7d7d18e92ec30977009e0947179dbc58ccb9e26 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Thu, 11 Feb 2021 23:39:11 +0530 Subject: [PATCH 05/45] shifted room avatar changing --- resources/qml/RoomSettings.qml | 47 ++++++++- src/dialogs/RoomSettingsOld.cpp | 14 +-- src/dialogs/RoomSettingsOld.h | 2 +- src/ui/RoomSettings.cpp | 167 +++++++++++++++++++++++++++++--- src/ui/RoomSettings.h | 35 ++++++- 5 files changed, 242 insertions(+), 23 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index ee824ba0..5d2baba1 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -33,12 +33,53 @@ ApplicationWindow { spacing: 10 Avatar { - url: "" + url: roomSettings.roomAvatarUrl.replace("mxc://", "image://MxcImage/") height: 130 width: 130 - displayName: "" - userid: "" Layout.alignment: Qt.AlignHCenter + onClicked: { + if(roomSettings.canChangeAvatar) { + roomSettings.updateAvatar(); + } + } + } + + BusyIndicator { + Layout.alignment: Qt.AlignHCenter + running: roomSettings.isLoading + visible: roomSettings.isLoading + } + + Text { + id: errorText + text: "Error Text" + color: "red" + visible: opacity > 0 + opacity: 0 + Layout.alignment: Qt.AlignHCenter + } + + SequentialAnimation { + id: hideErrorAnimation + running: false + PauseAnimation { + duration: 4000 + } + NumberAnimation { + target: errorText + property: 'opacity' + to: 0 + duration: 1000 + } + } + + Connections{ + target: roomSettings + onDisplayError: { + errorText.text = errorMessage + errorText.opacity = 1 + hideErrorAnimation.restart() + } } ColumnLayout { diff --git a/src/dialogs/RoomSettingsOld.cpp b/src/dialogs/RoomSettingsOld.cpp index 7eb34c20..bc34715e 100644 --- a/src/dialogs/RoomSettingsOld.cpp +++ b/src/dialogs/RoomSettingsOld.cpp @@ -143,10 +143,10 @@ EditModal::applyClicked() } 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); + auto proxy = std::make_shared(); + connect(proxy.get(), &ThreadProxya::topicEventSent, this, &EditModal::topicEventSent); + connect(proxy.get(), &ThreadProxya::nameEventSent, this, &EditModal::nameEventSent); + connect(proxy.get(), &ThreadProxya::error, this, &EditModal::error); if (newName != initialName_ && !newName.isEmpty()) { state::Name body; @@ -810,9 +810,9 @@ RoomSettingsOld::updateAvatar() // 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); + auto proxy = std::make_shared(); + connect(proxy.get(), &ThreadProxya::error, this, &RoomSettingsOld::displayErrorMessage); + connect(proxy.get(), &ThreadProxya::avatarChanged, this, &RoomSettingsOld::setAvatar); const auto bin = file.peek(file.size()); const auto payload = std::string(bin.data(), bin.size()); diff --git a/src/dialogs/RoomSettingsOld.h b/src/dialogs/RoomSettingsOld.h index e517676a..ad8dd5bd 100644 --- a/src/dialogs/RoomSettingsOld.h +++ b/src/dialogs/RoomSettingsOld.h @@ -40,7 +40,7 @@ protected: /// Convenience class which connects events emmited from threads /// outside of main with the UI code. -class ThreadProxy : public QObject +class ThreadProxya : public QObject { Q_OBJECT diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index 49e48e40..3ff1d5d5 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -1,5 +1,9 @@ #include "RoomSettings.h" +#include +#include +#include +#include #include #include @@ -84,6 +88,18 @@ RoomSettings::roomVersion() const return QString::fromStdString(info_.version); } +bool +RoomSettings::isLoading() const +{ + return isLoading_; +} + +QString +RoomSettings::roomAvatarUrl() +{ + return QString::fromStdString(info_.avatar_url); +} + int RoomSettings::memberCount() const { @@ -96,7 +112,6 @@ 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()); @@ -143,9 +158,9 @@ RoomSettings::enableEncryption() room_id, err->matrix_error.error, status_code); - //emit enableEncryptionError( - // tr("Failed to enable encryption: %1") - // .arg(QString::fromStdString(err->matrix_error.error))); + emit displayError( + tr("Failed to enable encryption: %1") + .arg(QString::fromStdString(err->matrix_error.error))); usesEncryption_ = false; emit encryptionChanged(); return; @@ -172,6 +187,33 @@ RoomSettings::canChangeJoinRules() const return false; } +bool +RoomSettings::canChangeNameAndTopic() const +{ + try { + return cache::hasEnoughPowerLevel({EventType::RoomName, EventType::RoomTopic}, + roomid_.toStdString(), + utils::localUser().toStdString()); + } catch (const lmdb::error &e) { + nhlog::db()->warn("lmdb error: {}", e.what()); + } + + return false; +} + +bool +RoomSettings::canChangeAvatar() const +{ + try { + return cache::hasEnoughPowerLevel( + {EventType::RoomAvatar}, roomid_.toStdString(), utils::localUser().toStdString()); + } catch (const lmdb::error &e) { + nhlog::db()->warn("lmdb error: {}", e.what()); + } + + return false; +} + bool RoomSettings::isEncryptionEnabled() const { @@ -269,8 +311,8 @@ RoomSettings::updateAccessRules(const std::string &room_id, const mtx::events::state::JoinRules &join_rule, const mtx::events::state::GuestAccess &guest_access) { - // startLoadingSpinner(); - // resetErrorLabel(); + isLoading_ = true; + emit loadingChanged(); http::client()->send_state_event( room_id, @@ -281,8 +323,9 @@ RoomSettings::updateAccessRules(const std::string &room_id, 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)); - + emit displayError(QString::fromStdString(err->matrix_error.error)); + isLoading_ = false; + emit loadingChanged(); return; } @@ -294,13 +337,115 @@ RoomSettings::updateAccessRules(const std::string &room_id, 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)); + emit displayError( + QString::fromStdString(err->matrix_error.error)); + } + isLoading_ = false; + emit loadingChanged(); + }); + }); +} + +void +RoomSettings::stopLoading() +{ + isLoading_ = false; + emit loadingChanged(); +} + +void +RoomSettings::avatarChanged() +{ + retrieveRoomInfo(); + emit avatarUrlChanged(); +} + +void +RoomSettings::updateAvatar() +{ + const QString picturesFolder = + QStandardPaths::writableLocation(QStandardPaths::PicturesLocation); + const QString fileName = QFileDialog::getOpenFileName( + nullptr, 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") { + emit displayError(tr("The selected file is not an image")); + return; + } + + if (!file.open(QIODevice::ReadOnly)) { + emit displayError(tr("Error while reading file: %1").arg(file.errorString())); + return; + } + + isLoading_ = true; + emit loadingChanged(); + + // 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::displayError); + connect(proxy.get(), &ThreadProxy::avatarChanged, this, &RoomSettings::avatarChanged); + connect(proxy.get(), &ThreadProxy::stopLoading, this, &RoomSettings::stopLoading); + + 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 = roomid_.toStdString(), + content = std::move(bin)](const mtx::responses::ContentURI &res, + mtx::http::RequestErr err) { + if (err) { + emit proxy->stopLoading(); + 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 signal that stops loading spinner and reset error label + emit proxy->stopLoading(); + emit proxy->avatarChanged(); }); }); } \ No newline at end of file diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index f5cc043c..09295a58 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -7,16 +7,34 @@ #include "CacheStructs.h" +/// 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(); + void stopLoading(); +}; + class RoomSettings : public QObject { - Q_OBJECT + Q_OBJECT Q_PROPERTY(QString roomName READ roomName CONSTANT) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT) + Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged) Q_PROPERTY(int memberCount READ memberCount CONSTANT) Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged) Q_PROPERTY(int accessJoinRules READ accessJoinRules NOTIFY accessJoinRulesChanged) + Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged) Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT) + Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT) + Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT) Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged) Q_PROPERTY(bool respondsToKeyRequests READ respondsToKeyRequests NOTIFY keyRequestsChanged) @@ -26,24 +44,38 @@ public: QString roomName() const; QString roomId() const; QString roomVersion() const; + QString roomAvatarUrl(); int memberCount() const; int notifications(); int accessJoinRules(); bool respondsToKeyRequests(); + bool isLoading() const; //! Whether the user has enough power level to send m.room.join_rules events. bool canChangeJoinRules() const; + //! Whether the user has enough power level to send m.room.name & m.room.topic events. + bool canChangeNameAndTopic() const; + //! Whether the user has enough power level to send m.room.avatar event. + bool canChangeAvatar() const; bool isEncryptionEnabled() const; Q_INVOKABLE void changeNotifications(int currentIndex); Q_INVOKABLE void changeAccessRules(int index); Q_INVOKABLE void changeKeyRequestsPreference(bool isOn); Q_INVOKABLE void enableEncryption(); + Q_INVOKABLE void updateAvatar(); signals: void notificationsChanged(); void accessJoinRulesChanged(); void keyRequestsChanged(); void encryptionChanged(); + void avatarUrlChanged(); + void loadingChanged(); + void displayError(const QString &errorMessage); + +public slots: + void avatarChanged(); + void stopLoading(); private: void retrieveRoomInfo(); @@ -54,6 +86,7 @@ private: private: QString roomid_; bool usesEncryption_ = false; + bool isLoading_ = false; RoomInfo info_; int notifications_ = 0; int accessRules_ = 0; From f044e2d2a11150d026d5a523b518027154602d3a Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Thu, 11 Feb 2021 23:50:45 +0530 Subject: [PATCH 06/45] fix avatar update on timeline sync --- resources/qml/TimelineView.qml | 2 +- src/timeline/TimelineModel.cpp | 8 ++++++++ src/timeline/TimelineModel.h | 3 +++ src/timeline/TimelineViewManager.cpp | 4 +--- src/timeline/TimelineViewManager.h | 2 -- src/ui/RoomSettings.cpp | 2 -- src/ui/RoomSettings.h | 1 - 7 files changed, 13 insertions(+), 9 deletions(-) diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 0c32effd..7a33f25f 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -175,7 +175,7 @@ Page { } Connections { - target: TimelineManager + target: TimelineManager.timeline onOpenRoomSettingsDialog: { var roomSettings = roomSettingsComponent.createObject(timelineRoot, { "roomSettings": settings diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 968ec3c7..29808fdd 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -807,6 +807,14 @@ TimelineModel::openUserProfile(QString userid, bool global) emit openProfile(userProfile); } +void +TimelineModel::openRoomSettings(QString roomid) +{ + RoomSettings *settings = new RoomSettings(roomid, this); + connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged); + openRoomSettingsDialog(settings); +} + void TimelineModel::replyAction(QString id) { diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 51b8049e..1a12a8c3 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -12,6 +12,7 @@ #include "EventStore.h" #include "InputBar.h" #include "ui/UserProfile.h" +#include "ui/RoomSettings.h" namespace mtx::http { using RequestErr = const std::optional &; @@ -213,6 +214,7 @@ public: Q_INVOKABLE void viewRawMessage(QString id) const; Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void openUserProfile(QString userid, bool global = false); + Q_INVOKABLE void openRoomSettings(QString roomid); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; Q_INVOKABLE void redactEvent(QString id); @@ -296,6 +298,7 @@ signals: void newCallEvent(const mtx::events::collections::TimelineEvents &event); void openProfile(UserProfile *profile); + void openRoomSettingsDialog(RoomSettings *settings); void newMessageToSend(mtx::events::collections::TimelineEvents event); void addPendingMessageToStore(mtx::events::collections::TimelineEvents event); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 4edc3369..99a2e388 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -399,9 +399,7 @@ void TimelineViewManager::openRoomSettings() { MainWindow::instance()->openRoomSettings(timeline_->roomId()); - - RoomSettings *settings = new RoomSettings(timeline_->roomId(), this); - emit openRoomSettingsDialog(settings); + timeline_->openRoomSettings(timeline_->roomId()); } void diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index 10708033..a2d37342 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -19,7 +19,6 @@ #include "emoji/EmojiModel.h" #include "emoji/Provider.h" #include "dialogs/RoomSettingsOld.h" -#include "ui/RoomSettings.h" class MxcImageProvider; class BlurhashProvider; @@ -89,7 +88,6 @@ signals: void showRoomList(); void narrowViewChanged(); void focusChanged(); - void openRoomSettingsDialog(RoomSettings *settings); 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 index 3ff1d5d5..adf8d8a7 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -395,7 +395,6 @@ RoomSettings::updateAvatar() // be queued back into the UI thread through this proxy object. auto proxy = std::make_shared(); connect(proxy.get(), &ThreadProxy::error, this, &RoomSettings::displayError); - connect(proxy.get(), &ThreadProxy::avatarChanged, this, &RoomSettings::avatarChanged); connect(proxy.get(), &ThreadProxy::stopLoading, this, &RoomSettings::stopLoading); const auto bin = file.peek(file.size()); @@ -445,7 +444,6 @@ RoomSettings::updateAvatar() } emit proxy->stopLoading(); - emit proxy->avatarChanged(); }); }); } \ No newline at end of file diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 09295a58..d31b38f2 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -15,7 +15,6 @@ class ThreadProxy : public QObject signals: void error(const QString &msg); - void avatarChanged(); void nameEventSent(const QString &); void topicEventSent(); void stopLoading(); From 35aa0126ac80d99aaf7f61d6eeba6c7d9eca96bc Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Fri, 12 Feb 2021 12:48:12 +0530 Subject: [PATCH 07/45] added changing of name through edit modal, removed old roomsettings --- CMakeLists.txt | 2 - resources/qml/RoomSettings.qml | 9 +- resources/qml/TopBar.qml | 4 +- resources/qml/UserProfile.qml | 1 - src/MainWindow.cpp | 9 - src/MainWindow.h | 1 - src/dialogs/RoomSettingsOld.cpp | 865 --------------------------- src/dialogs/RoomSettingsOld.h | 150 ----- src/timeline/TimelineModel.cpp | 4 +- src/timeline/TimelineModel.h | 2 +- src/timeline/TimelineViewManager.cpp | 6 - src/timeline/TimelineViewManager.h | 2 - src/ui/RoomSettings.cpp | 164 +++++ src/ui/RoomSettings.h | 41 +- 14 files changed, 214 insertions(+), 1046 deletions(-) delete mode 100644 src/dialogs/RoomSettingsOld.cpp delete mode 100644 src/dialogs/RoomSettingsOld.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 505a59f1..3e41ebc8 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -257,7 +257,6 @@ set(SRC_FILES src/dialogs/PreviewUploadOverlay.cpp src/dialogs/ReCaptcha.cpp src/dialogs/ReadReceipts.cpp - src/dialogs/RoomSettingsOld.cpp # Emoji src/emoji/EmojiModel.cpp @@ -472,7 +471,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/RawMessage.h src/dialogs/ReCaptcha.h src/dialogs/ReadReceipts.h - src/dialogs/RoomSettingsOld.h # Emoji src/emoji/EmojiModel.h diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 5d2baba1..eabe68f5 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -12,13 +12,11 @@ ApplicationWindow { x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) - height: 600 - width: 420 - minimumHeight: 420 + minimumWidth: 340 + minimumHeight: 600 palette: colors color: colors.window modality: Qt.WindowModal - flags: Qt.WindowStaysOnTopHint Shortcut { sequence: StandardKey.Cancel @@ -100,6 +98,8 @@ ApplicationWindow { ImageButton { Layout.alignment: Qt.AlignHCenter image: ":/icons/icons/ui/edit.png" + visible: roomSettings.canChangeNameAndTopic + onClicked: roomSettings.openEditModal() } MatrixText { @@ -248,6 +248,7 @@ ApplicationWindow { Button { Layout.alignment: Qt.AlignRight text: "Ok" + onClicked: close() } } } \ No newline at end of file diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index 273ed8ab..c64eddd4 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -68,7 +68,7 @@ Rectangle { MouseArea { anchors.fill: parent - onClicked: TimelineManager.openRoomSettings() + onClicked: TimelineManager.timeline.openRoomSettings() } } @@ -114,7 +114,7 @@ Rectangle { MenuItem { text: qsTr("Settings") - onTriggered: TimelineManager.openRoomSettings() + onTriggered: TimelineManager.timeline.openRoomSettings() } } diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 37ae6de8..e8d41073 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -113,7 +113,6 @@ ApplicationWindow { } } } - } MatrixText { diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index b1635c94..ae532ef3 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -51,7 +51,6 @@ #include "dialogs/Logout.h" #include "dialogs/MemberList.h" #include "dialogs/ReadReceipts.h" -#include "dialogs/RoomSettingsOld.h" MainWindow *MainWindow::instance_ = nullptr; @@ -363,14 +362,6 @@ MainWindow::hasActiveUser() settings.contains(prefix + "auth/user_id"); } -void -MainWindow::openRoomSettings(const QString &room_id) -{ - auto dialog = new dialogs::RoomSettingsOld(room_id, this); - - showDialog(dialog); -} - void MainWindow::openMemberListDialog(const QString &room_id) { diff --git a/src/MainWindow.h b/src/MainWindow.h index b3983d72..5c2df8b6 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -78,7 +78,6 @@ public: std::function callback); void openJoinRoomDialog(std::function callback); void openLogoutDialog(); - void openRoomSettings(const QString &room_id); void openMemberListDialog(const QString &room_id); void openReadReceiptsDialog(const QString &event_id); diff --git a/src/dialogs/RoomSettingsOld.cpp b/src/dialogs/RoomSettingsOld.cpp deleted file mode 100644 index bc34715e..00000000 --- a/src/dialogs/RoomSettingsOld.cpp +++ /dev/null @@ -1,865 +0,0 @@ -#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(), &ThreadProxya::topicEventSent, this, &EditModal::topicEventSent); - connect(proxy.get(), &ThreadProxya::nameEventSent, this, &EditModal::nameEventSent); - connect(proxy.get(), &ThreadProxya::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(), &ThreadProxya::error, this, &RoomSettingsOld::displayErrorMessage); - connect(proxy.get(), &ThreadProxya::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 deleted file mode 100644 index ad8dd5bd..00000000 --- a/src/dialogs/RoomSettingsOld.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 ThreadProxya : 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/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 29808fdd..efeba146 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -808,9 +808,9 @@ TimelineModel::openUserProfile(QString userid, bool global) } void -TimelineModel::openRoomSettings(QString roomid) +TimelineModel::openRoomSettings() { - RoomSettings *settings = new RoomSettings(roomid, this); + RoomSettings *settings = new RoomSettings(roomId(), this); connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged); openRoomSettingsDialog(settings); } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 1a12a8c3..29207318 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -214,7 +214,7 @@ public: Q_INVOKABLE void viewRawMessage(QString id) const; Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void openUserProfile(QString userid, bool global = false); - Q_INVOKABLE void openRoomSettings(QString roomid); + Q_INVOKABLE void openRoomSettings(); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; Q_INVOKABLE void redactEvent(QString id); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 99a2e388..0ed680f8 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -395,12 +395,6 @@ TimelineViewManager::openLeaveRoomDialog() const { MainWindow::instance()->openLeaveRoomDialog(timeline_->roomId()); } -void -TimelineViewManager::openRoomSettings() -{ - MainWindow::instance()->openRoomSettings(timeline_->roomId()); - timeline_->openRoomSettings(timeline_->roomId()); -} void TimelineViewManager::verifyUser(QString userid) diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h index a2d37342..3e58bb43 100644 --- a/src/timeline/TimelineViewManager.h +++ b/src/timeline/TimelineViewManager.h @@ -18,7 +18,6 @@ #include "WebRTCSession.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" -#include "dialogs/RoomSettingsOld.h" class MxcImageProvider; class BlurhashProvider; @@ -70,7 +69,6 @@ public: Q_INVOKABLE void openInviteUsersDialog(); Q_INVOKABLE void openMemberListDialog() const; Q_INVOKABLE void openLeaveRoomDialog() const; - Q_INVOKABLE void openRoomSettings(); Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); void verifyUser(QString userid); diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index adf8d8a7..c7f388d4 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -4,16 +4,165 @@ #include #include #include +#include +#include +#include #include #include #include "Cache.h" #include "Logging.h" +#include "Config.h" #include "MatrixClient.h" +#include "ui/TextField.h" #include "Utils.h" using namespace mtx::events; +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(); + + if (window != nullptr) { + 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(QString roomid, QObject *parent) : roomid_{std::move(roomid)} , QObject(parent) @@ -220,6 +369,21 @@ RoomSettings::isEncryptionEnabled() const return usesEncryption_; } +void +RoomSettings::openEditModal() +{ + retrieveRoomInfo(); + + auto modal = new EditModal(roomid_); + modal->setFields(QString::fromStdString(info_.name), QString::fromStdString(info_.topic)); + modal->raise(); + modal->show(); + connect(modal, &EditModal::nameChanged, this, [this](const QString &newName) { + info_.name = newName.toStdString(); + emit roomNameChanged(); + }); +} + void RoomSettings::changeNotifications(int currentIndex) { diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index d31b38f2..0d0b13f6 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -1,12 +1,16 @@ #pragma once +#include #include +#include #include #include #include "CacheStructs.h" +class TextField; + /// Convenience class which connects events emmited from threads /// outside of main with the UI code. class ThreadProxy : public QObject @@ -20,10 +24,43 @@ signals: void stopLoading(); }; +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_; +}; + class RoomSettings : public QObject { Q_OBJECT - Q_PROPERTY(QString roomName READ roomName CONSTANT) + Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT) Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged) @@ -62,6 +99,7 @@ public: Q_INVOKABLE void changeKeyRequestsPreference(bool isOn); Q_INVOKABLE void enableEncryption(); Q_INVOKABLE void updateAvatar(); + Q_INVOKABLE void openEditModal(); signals: void notificationsChanged(); @@ -69,6 +107,7 @@ signals: void keyRequestsChanged(); void encryptionChanged(); void avatarUrlChanged(); + void roomNameChanged(); void loadingChanged(); void displayError(const QString &errorMessage); From f3596aed554feb848b3f3d09239e5cacf2155024 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Sat, 13 Feb 2021 19:08:52 +0530 Subject: [PATCH 08/45] added room topic --- resources/qml/RoomSettings.qml | 37 ++++++++++++++++++++++++++++++---- resources/qml/TopBar.qml | 2 +- src/ui/RoomSettings.cpp | 20 ++++++++++++++---- src/ui/RoomSettings.h | 32 ++++++++++++++++------------- 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index eabe68f5..2f6f1866 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -12,8 +12,8 @@ ApplicationWindow { x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) - minimumWidth: 340 - minimumHeight: 600 + minimumWidth: 400 + minimumHeight: 650 palette: colors color: colors.window modality: Qt.WindowModal @@ -24,9 +24,12 @@ ApplicationWindow { } ColumnLayout { - id: contentLayout + id: contentLayout1 - anchors.fill: parent + anchors.left: parent.left + anchors.right: parent.right + anchors.top: parent.top + anchors.bottom: undefined anchors.margins: 10 spacing: 10 @@ -101,6 +104,32 @@ ApplicationWindow { visible: roomSettings.canChangeNameAndTopic onClicked: roomSettings.openEditModal() } + } + + ScrollView { + id: topicScroll + anchors.left: parent.left + anchors.right: parent.right + anchors.top: contentLayout1.bottom + anchors.bottom: undefined + anchors.margins: 10 + height: 100 + + TextArea { + text: roomSettings.roomTopic + background: null + } + } + + ColumnLayout { + id: contentLayout2 + + anchors.left: parent.left + anchors.right: parent.right + anchors.top: topicScroll.bottom + anchors.bottom: parent.bottom + anchors.margins: 10 + spacing: 10 MatrixText { text: "SETTINGS" diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml index c64eddd4..967aa11e 100644 --- a/resources/qml/TopBar.qml +++ b/resources/qml/TopBar.qml @@ -15,7 +15,7 @@ Rectangle { MouseArea { anchors.fill: parent - onClicked: TimelineManager.openRoomSettings() + onClicked: TimelineManager.timeline.openRoomSettings() } GridLayout { diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index c7f388d4..d2b5a630 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -75,9 +75,10 @@ EditModal::EditModal(const QString &roomId, QWidget *parent) } void -EditModal::topicEventSent() +EditModal::topicEventSent(const QString &topic) { errorField_->hide(); + emit topicChanged(topic); close(); } @@ -141,14 +142,14 @@ EditModal::applyClicked() http::client()->send_state_event( roomId_.toStdString(), body, - [proxy](const mtx::responses::EventId &, mtx::http::RequestErr err) { + [proxy, newTopic](const mtx::responses::EventId &, mtx::http::RequestErr err) { if (err) { emit proxy->error( QString::fromStdString(err->matrix_error.error)); return; } - emit proxy->topicEventSent(); + emit proxy->topicEventSent(newTopic); }); } } @@ -222,7 +223,13 @@ RoomSettings::RoomSettings(QString roomid, QObject *parent) QString RoomSettings::roomName() const { - return QString(info_.name.c_str()); + return QString::fromStdString(info_.name); +} + +QString +RoomSettings::roomTopic() const +{ + return QString::fromStdString(info_.topic); } QString @@ -382,6 +389,11 @@ RoomSettings::openEditModal() info_.name = newName.toStdString(); emit roomNameChanged(); }); + + connect(modal, &EditModal::topicChanged, this, [this](const QString &newTopic) { + info_.topic = newTopic.toStdString(); + emit roomTopicChanged(); + }); } void diff --git a/src/ui/RoomSettings.h b/src/ui/RoomSettings.h index 0d0b13f6..25c6e588 100644 --- a/src/ui/RoomSettings.h +++ b/src/ui/RoomSettings.h @@ -20,7 +20,7 @@ class ThreadProxy : public QObject signals: void error(const QString &msg); void nameEventSent(const QString &); - void topicEventSent(); + void topicEventSent(const QString &); void stopLoading(); }; @@ -35,9 +35,10 @@ public: signals: void nameChanged(const QString &roomName); + void topicChanged(const QString &topic); private slots: - void topicEventSent(); + void topicEventSent(const QString &topic); void nameEventSent(const QString &name); void error(const QString &msg); @@ -60,25 +61,27 @@ private: class RoomSettings : public QObject { Q_OBJECT - Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) Q_PROPERTY(QString roomId READ roomId CONSTANT) Q_PROPERTY(QString roomVersion READ roomVersion CONSTANT) + Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged) + Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged) Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY avatarUrlChanged) Q_PROPERTY(int memberCount READ memberCount CONSTANT) Q_PROPERTY(int notifications READ notifications NOTIFY notificationsChanged) Q_PROPERTY(int accessJoinRules READ accessJoinRules NOTIFY accessJoinRulesChanged) Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged) + Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT) Q_PROPERTY(bool canChangeJoinRules READ canChangeJoinRules CONSTANT) Q_PROPERTY(bool canChangeNameAndTopic READ canChangeNameAndTopic CONSTANT) - Q_PROPERTY(bool canChangeAvatar READ canChangeAvatar CONSTANT) Q_PROPERTY(bool isEncryptionEnabled READ isEncryptionEnabled NOTIFY encryptionChanged) Q_PROPERTY(bool respondsToKeyRequests READ respondsToKeyRequests NOTIFY keyRequestsChanged) public: RoomSettings(QString roomid, QObject *parent = nullptr); - QString roomName() const; QString roomId() const; + QString roomName() const; + QString roomTopic() const; QString roomVersion() const; QString roomAvatarUrl(); int memberCount() const; @@ -94,26 +97,27 @@ public: bool canChangeAvatar() const; bool isEncryptionEnabled() const; - Q_INVOKABLE void changeNotifications(int currentIndex); - Q_INVOKABLE void changeAccessRules(int index); - Q_INVOKABLE void changeKeyRequestsPreference(bool isOn); Q_INVOKABLE void enableEncryption(); Q_INVOKABLE void updateAvatar(); Q_INVOKABLE void openEditModal(); + Q_INVOKABLE void changeAccessRules(int index); + Q_INVOKABLE void changeNotifications(int currentIndex); + Q_INVOKABLE void changeKeyRequestsPreference(bool isOn); signals: + void loadingChanged(); + void roomNameChanged(); + void roomTopicChanged(); + void avatarUrlChanged(); + void encryptionChanged(); + void keyRequestsChanged(); void notificationsChanged(); void accessJoinRulesChanged(); - void keyRequestsChanged(); - void encryptionChanged(); - void avatarUrlChanged(); - void roomNameChanged(); - void loadingChanged(); void displayError(const QString &errorMessage); public slots: - void avatarChanged(); void stopLoading(); + void avatarChanged(); private: void retrieveRoomInfo(); From 65403521239c202e7a3cdfb5a52fbcb700656474 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Sat, 13 Feb 2021 20:46:40 +0530 Subject: [PATCH 09/45] fix roomsetting layout --- resources/qml/RoomSettings.qml | 48 ++++++++++++---------------------- resources/res.qrc | 1 + 2 files changed, 18 insertions(+), 31 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 2f6f1866..bc9406d4 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -26,10 +26,7 @@ ApplicationWindow { ColumnLayout { id: contentLayout1 - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.bottom: undefined + anchors.fill: parent anchors.margins: 10 spacing: 10 @@ -104,32 +101,21 @@ ApplicationWindow { visible: roomSettings.canChangeNameAndTopic onClicked: roomSettings.openEditModal() } - } - ScrollView { - id: topicScroll - anchors.left: parent.left - anchors.right: parent.right - anchors.top: contentLayout1.bottom - anchors.bottom: undefined - anchors.margins: 10 - height: 100 + ScrollView { + Layout.maximumHeight: 75 + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOn + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true - TextArea { - text: roomSettings.roomTopic - background: null + TextArea { + text: roomSettings.roomTopic + wrapMode: TextEdit.WordWrap + readOnly: true + background: null + } } - } - - ColumnLayout { - id: contentLayout2 - - anchors.left: parent.left - anchors.right: parent.right - anchors.top: topicScroll.bottom - anchors.bottom: parent.bottom - anchors.margins: 10 - spacing: 10 MatrixText { text: "SETTINGS" @@ -178,8 +164,8 @@ ApplicationWindow { Layout.fillWidth: true } - Switch { - id: encryptionSwitch + ToggleButton { + id: encryptionToggle checked: roomSettings.isEncryptionEnabled onToggled: { @@ -209,7 +195,7 @@ ApplicationWindow { } onRejected: { - encryptionSwitch.checked = false + encryptionToggle.checked = false } standardButtons: Dialog.Ok | Dialog.Cancel @@ -227,7 +213,7 @@ ApplicationWindow { Layout.fillWidth: true } - Switch { + ToggleButton { ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys upon request. Use with caution, this is a temporary measure to test the E2E implementation until device verification is completed.") diff --git a/resources/res.qrc b/resources/res.qrc index 24b41179..12d098c0 100644 --- a/resources/res.qrc +++ b/resources/res.qrc @@ -128,6 +128,7 @@ qml/EncryptionIndicator.qml qml/ImageButton.qml qml/MatrixText.qml + qml/ToggleButton.qml qml/MessageInput.qml qml/MessageView.qml qml/NhekoBusyIndicator.qml From 4996ae27a0f8cf2a9e2c3834cee3e546239a486a Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Sat, 13 Feb 2021 21:49:21 +0530 Subject: [PATCH 10/45] added togglebutton styling --- resources/qml/RoomSettings.qml | 5 ++--- resources/qml/ToggleButton.qml | 22 +++++++++++++++++----- 2 files changed, 19 insertions(+), 8 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index bc9406d4..b3602a6a 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -105,7 +105,6 @@ ApplicationWindow { ScrollView { Layout.maximumHeight: 75 ScrollBar.horizontal.policy: ScrollBar.AlwaysOff - ScrollBar.vertical.policy: ScrollBar.AlwaysOn Layout.alignment: Qt.AlignHCenter Layout.fillWidth: true @@ -168,7 +167,7 @@ ApplicationWindow { id: encryptionToggle checked: roomSettings.isEncryptionEnabled - onToggled: { + onClicked: { if(roomSettings.isEncryptionEnabled) { checked=true; return; @@ -220,7 +219,7 @@ ApplicationWindow { checked: roomSettings.respondsToKeyRequests - onToggled: { + onClicked: { roomSettings.changeKeyRequestsPreference(checked) } } diff --git a/resources/qml/ToggleButton.qml b/resources/qml/ToggleButton.qml index 584fc693..cf67c48c 100644 --- a/resources/qml/ToggleButton.qml +++ b/resources/qml/ToggleButton.qml @@ -1,10 +1,22 @@ import QtQuick 2.5 -import QtQuick.Controls 2.3 +import QtQuick.Controls 1.4 +import QtQuick.Controls.Styles 1.4 import im.nheko 1.0 Switch { - property color activeColor - property color disabledColor - property color inactiveColor - property color trackColor + style: SwitchStyle { + handle: Rectangle { + width: 20 + height: 20 + radius: 90 + color: "whitesmoke" + } + + groove: Rectangle { + implicitWidth: 40 + implicitHeight: 20 + radius: 90 + color: checked ? "skyblue" : "grey" + } + } } \ No newline at end of file From 1a406f79e6c7f673172c07da30ab958e980d919c Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Sat, 13 Feb 2021 23:59:42 +0530 Subject: [PATCH 11/45] replaced with togglebutton using qtquickcontrols2 --- resources/qml/ToggleButton.qml | 36 ++++++++++++++++++++++------------ src/MainWindow.h | 1 - src/timeline/TimelineModel.h | 2 +- src/ui/RoomSettings.cpp | 8 ++++---- 4 files changed, 28 insertions(+), 19 deletions(-) diff --git a/resources/qml/ToggleButton.qml b/resources/qml/ToggleButton.qml index cf67c48c..01aef4c6 100644 --- a/resources/qml/ToggleButton.qml +++ b/resources/qml/ToggleButton.qml @@ -1,22 +1,32 @@ import QtQuick 2.5 -import QtQuick.Controls 1.4 -import QtQuick.Controls.Styles 1.4 +import QtQuick 2.12 +import QtQuick.Controls 2.12 import im.nheko 1.0 Switch { - style: SwitchStyle { - handle: Rectangle { - width: 20 - height: 20 - radius: 90 - color: "whitesmoke" + id: toggleButton + + indicator: Item { + implicitWidth: 48 + implicitHeight: 26 + + Rectangle { + height: parent.height/2 + radius: height/2 + width: parent.width - height + x: radius + y: parent.height / 2 - height / 2 + color: toggleButton.checked ? "skyblue" : "grey" + border.color: "#cccccc" } - groove: Rectangle { - implicitWidth: 40 - implicitHeight: 20 - radius: 90 - color: checked ? "skyblue" : "grey" + Rectangle { + x: toggleButton.checked ? parent.width - width : 0 + width: parent.height + height: width + radius: width/2 + color: toggleButton.down ? "whitesmoke" : "whitesmoke" + border.color: "#999999" } } } \ No newline at end of file diff --git a/src/MainWindow.h b/src/MainWindow.h index 5c2df8b6..4a8ea642 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -54,7 +54,6 @@ class LeaveRoom; class Logout; class MemberList; class ReCaptcha; -class RoomSettingsOld; } class MainWindow : public QMainWindow diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 29207318..df067fd4 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -11,8 +11,8 @@ #include "CacheCryptoStructs.h" #include "EventStore.h" #include "InputBar.h" -#include "ui/UserProfile.h" #include "ui/RoomSettings.h" +#include "ui/UserProfile.h" namespace mtx::http { using RequestErr = const std::optional &; diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index d2b5a630..b166332c 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -1,21 +1,21 @@ #include "RoomSettings.h" +#include #include +#include #include #include #include -#include #include -#include #include #include #include "Cache.h" -#include "Logging.h" #include "Config.h" +#include "Logging.h" #include "MatrixClient.h" -#include "ui/TextField.h" #include "Utils.h" +#include "ui/TextField.h" using namespace mtx::events; From b5e351ab025fe751cd230371714cdee24125bfc4 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Sun, 14 Feb 2021 11:26:10 +0530 Subject: [PATCH 12/45] Replace rowlayouts with gridlayout and fix room settings initializer list --- resources/qml/RoomSettings.qml | 61 ++++++++++++++-------------------- src/ui/RoomSettings.cpp | 4 +-- 2 files changed, 27 insertions(+), 38 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index b3602a6a..eebecebb 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -12,7 +12,7 @@ ApplicationWindow { x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) - minimumWidth: 400 + minimumWidth: 420 minimumHeight: 650 palette: colors color: colors.window @@ -113,56 +113,55 @@ ApplicationWindow { wrapMode: TextEdit.WordWrap readOnly: true background: null + horizontalAlignment: TextEdit.AlignHCenter + selectByMouse: true } } - MatrixText { - text: "SETTINGS" - } + GridLayout { + columns: 2 - RowLayout { MatrixText { - text: "Notifications" + text: "SETTINGS" } Item { Layout.fillWidth: true } + MatrixText { + text: "Notifications" + Layout.fillWidth: true + } + ComboBox { model: [ "Muted", "Mentions only", "All messages" ] currentIndex: roomSettings.notifications onActivated: { roomSettings.changeNotifications(index) } + Layout.fillWidth: true } - } - RowLayout { MatrixText { text: "Room access" + Layout.fillWidth: true } ComboBox { - Layout.fillWidth: true enabled: roomSettings.canChangeJoinRules model: [ "Anyone and guests", "Anyone", "Invited users" ] currentIndex: roomSettings.accessJoinRules onActivated: { roomSettings.changeAccessRules(index) } + Layout.fillWidth: true } - } - RowLayout { MatrixText { text: "Encryption" } - Item { - Layout.fillWidth: true - } - ToggleButton { id: encryptionToggle @@ -175,6 +174,7 @@ ApplicationWindow { confirmEncryptionDialog.open(); } + Layout.alignment: Qt.AlignRight } MessageDialog { @@ -199,20 +199,14 @@ ApplicationWindow { standardButtons: Dialog.Ok | Dialog.Cancel } - } - - RowLayout { - visible: roomSettings.isEncryptionEnabled MatrixText { + visible: roomSettings.isEncryptionEnabled text: "Respond to key requests" } - Item { - Layout.fillWidth: true - } - ToggleButton { + visible: roomSettings.isEncryptionEnabled ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys upon request. Use with caution, this is a temporary measure to test the E2E implementation until device verification is completed.") @@ -222,40 +216,35 @@ ApplicationWindow { onClicked: { roomSettings.changeKeyRequestsPreference(checked) } + Layout.alignment: Qt.AlignRight } - } - MatrixText { - text: "INFO" - } - - RowLayout { MatrixText { - text: "Internal ID" + text: "INFO" } Item { Layout.fillWidth: true } + MatrixText { + text: "Internal ID" + } + MatrixText { text: roomSettings.roomId font.pixelSize: 12 + Layout.alignment: Qt.AlignRight } - } - RowLayout { MatrixText { text: "Room Version" } - Item { - Layout.fillWidth: true - } - MatrixText { text: roomSettings.roomVersion font.pixelSize: 12 + Layout.alignment: Qt.AlignRight } } diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index b166332c..aa6f60a0 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -165,8 +165,8 @@ EditModal::setFields(const QString &roomName, const QString &roomTopic) } RoomSettings::RoomSettings(QString roomid, QObject *parent) - : roomid_{std::move(roomid)} - , QObject(parent) + : QObject(parent) + , roomid_{std::move(roomid)} { retrieveRoomInfo(); From 19dbbb2c6cffdf0697635f08acf3af9138aed7b9 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Tue, 16 Feb 2021 00:47:17 +0530 Subject: [PATCH 13/45] add rooms model, add room delegate for completer --- CMakeLists.txt | 2 ++ resources/qml/Completer.qml | 29 +++++++++++++++++ resources/qml/MessageInput.qml | 3 ++ src/RoomsModel.cpp | 57 ++++++++++++++++++++++++++++++++++ src/RoomsModel.h | 33 ++++++++++++++++++++ src/timeline/InputBar.cpp | 6 ++++ 6 files changed, 130 insertions(+) create mode 100644 src/RoomsModel.cpp create mode 100644 src/RoomsModel.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 72190947..54fbd7b6 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -326,6 +326,7 @@ set(SRC_FILES src/UserInfoWidget.cpp src/UserSettingsPage.cpp src/UsersModel.cpp + src/RoomsModel.cpp src/Utils.cpp src/WebRTCSession.cpp src/WelcomePage.cpp @@ -537,6 +538,7 @@ qt5_wrap_cpp(MOC_HEADERS src/UserInfoWidget.h src/UserSettingsPage.h src/UsersModel.h + src/RoomsModel.h src/WebRTCSession.h src/WelcomePage.h src/popups/PopupItem.h diff --git a/resources/qml/Completer.qml b/resources/qml/Completer.qml index 27322172..f77f50e9 100644 --- a/resources/qml/Completer.qml +++ b/resources/qml/Completer.qml @@ -154,6 +154,35 @@ Popup { } + DelegateChoice { + roleValue: "room" + + RowLayout { + id: del + + anchors.centerIn: parent + + Avatar { + height: 24 + width: 24 + url: model.avatarUrl.replace("mxc://", "image://MxcImage/") + onClicked: popup.completionClicked(completer.completionAt(model.index)) + } + + Label { + text: model.roomName + color: model.index == popup.currentIndex ? colors.highlightedText : colors.text + } + + Label { + text: "(" + model.roomAlias + ")" + color: model.index == popup.currentIndex ? colors.highlightedText : colors.buttonText + } + + } + + } + } } diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index b5c96660..7ecaf81a 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -183,6 +183,9 @@ Rectangle { } else if (event.key == Qt.Key_Colon) { messageInput.openCompleter(cursorPosition, "emoji"); popup.open(); + } else if (event.key == Qt.Key_NumberSign) { + messageInput.openCompleter(cursorPosition, "room"); + popup.open(); } else if (event.key == Qt.Key_Escape && popup.opened) { completerTriggeredAt = -1; popup.completerName = ""; diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp new file mode 100644 index 00000000..f79c5515 --- /dev/null +++ b/src/RoomsModel.cpp @@ -0,0 +1,57 @@ +#include "RoomsModel.h" + +#include "Cache_p.h" +#include "CompletionModelRoles.h" + +RoomsModel::RoomsModel(QObject *parent) + : QAbstractListModel(parent) +{ + rooms_ = cache::joinedRooms(); + roomInfos = cache::getRoomInfo(rooms_); + + for (const auto &r : rooms_) { + auto roomAliasesList = cache::client()->getRoomAliases(r); + + if (roomAliasesList) { + roomAliases.push_back(QString::fromStdString(roomAliasesList->alias)); + roomids.push_back(QString::fromStdString(r)); + } + } +} + +QHash +RoomsModel::roleNames() const +{ + return {{CompletionModel::CompletionRole, "completionRole"}, + {CompletionModel::SearchRole, "searchRole"}, + {CompletionModel::SearchRole2, "searchRole2"}, + {Roles::RoomAlias, "roomAlias"}, + {Roles::AvatarUrl, "avatarUrl"}, + {Roles::RoomID, "roomid"}, + {Roles::RoomName, "roomName"}}; +} + +QVariant +RoomsModel::data(const QModelIndex &index, int role) const +{ + if (hasIndex(index.row(), index.column(), index.parent())) { + switch (role) { + case CompletionModel::CompletionRole: + return QString("%1").arg(roomAliases[index.row()]); + case CompletionModel::SearchRole: + case Qt::DisplayRole: + case Roles::RoomAlias: + return roomAliases[index.row()]; + case CompletionModel::SearchRole2: + return roomAliases[index.row()]; + case Roles::AvatarUrl: + return QString::fromStdString( + roomInfos.at(roomids[index.row()]).avatar_url); + case Roles::RoomID: + return roomids[index.row()]; + case Roles::RoomName: + return QString::fromStdString(roomInfos.at(roomids[index.row()]).name); + } + } + return {}; +} diff --git a/src/RoomsModel.h b/src/RoomsModel.h new file mode 100644 index 00000000..88cb5c68 --- /dev/null +++ b/src/RoomsModel.h @@ -0,0 +1,33 @@ +#pragma once + +#include "Cache.h" + +#include +#include + +class RoomsModel : public QAbstractListModel +{ +public: + enum Roles + { + AvatarUrl = Qt::UserRole, + RoomAlias, + RoomID, + RoomName, + }; + + RoomsModel(QObject *parent = nullptr); + QHash roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const override + { + (void)parent; + return (int)roomAliases.size(); + } + QVariant data(const QModelIndex &index, int role) const override; + +private: + std::vector rooms_; + std::vector roomids; + std::vector roomAliases; + std::map roomInfos; +}; diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 08cbd15b..49fa5249 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -23,6 +23,7 @@ #include "TimelineViewManager.h" #include "UserSettingsPage.h" #include "UsersModel.h" +#include "RoomsModel.h" #include "Utils.h" #include "dialogs/PreviewUploadOverlay.h" #include "emoji/EmojiModel.h" @@ -186,6 +187,11 @@ InputBar::completerFor(QString completerName) auto proxy = new CompletionProxyModel(emojiModel); emojiModel->setParent(proxy); return proxy; + } else if (completerName == "room") { + auto roomModel = new RoomsModel(); + auto proxy = new CompletionProxyModel(roomModel); + roomModel->setParent(proxy); + return proxy; } return nullptr; } From 8c4f0a070e6e592e63726079bb43118614843fc3 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Tue, 16 Feb 2021 11:52:03 +0530 Subject: [PATCH 14/45] change togglebutton size, set textarea color to colors.text --- resources/qml/RoomSettings.qml | 3 ++- resources/qml/ToggleButton.qml | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index eebecebb..dd5c85d3 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -113,8 +113,9 @@ ApplicationWindow { wrapMode: TextEdit.WordWrap readOnly: true background: null - horizontalAlignment: TextEdit.AlignHCenter selectByMouse: true + color: colors.text + horizontalAlignment: TextEdit.AlignHCenter } } diff --git a/resources/qml/ToggleButton.qml b/resources/qml/ToggleButton.qml index 01aef4c6..59ec1a12 100644 --- a/resources/qml/ToggleButton.qml +++ b/resources/qml/ToggleButton.qml @@ -8,10 +8,11 @@ Switch { indicator: Item { implicitWidth: 48 - implicitHeight: 26 + implicitHeight: 24 + y: parent.height / 2 - height / 2 Rectangle { - height: parent.height/2 + height: 3 * parent.height/4 radius: height/2 width: parent.width - height x: radius @@ -22,11 +23,12 @@ Switch { Rectangle { x: toggleButton.checked ? parent.width - width : 0 + y: parent.height / 2 - height / 2 width: parent.height height: width radius: width/2 color: toggleButton.down ? "whitesmoke" : "whitesmoke" - border.color: "#999999" + border.color: "#ebebeb" } } } \ No newline at end of file From a1cd55917bb701ce492bb313b1809beb9fcafcd7 Mon Sep 17 00:00:00 2001 From: Ikko Ashimine Date: Tue, 16 Feb 2021 23:38:56 +0900 Subject: [PATCH 15/45] GitHub format Github -> GitHub --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b9690fc5..4f8aa111 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,7 @@ Specifically there is support for: ### Releases Releases for Linux (AppImage), macOS (disk image) & Windows (x64 installer) -can be found in the [Github releases](https://github.com/Nheko-Reborn/nheko/releases). +can be found in the [GitHub releases](https://github.com/Nheko-Reborn/nheko/releases). ### Repositories From f76f7b7f8adaf6fd1727b30c0e1af5e5adacfe7b Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Tue, 16 Feb 2021 22:22:55 +0530 Subject: [PATCH 16/45] fixed roomsettings spacing and toggle button right align bug --- resources/qml/RoomSettings.qml | 17 +++++++++++++++-- resources/qml/ToggleButton.qml | 2 ++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index dd5c85d3..898853e9 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -121,9 +121,11 @@ ApplicationWindow { GridLayout { columns: 2 + rowSpacing: 10 MatrixText { text: "SETTINGS" + font.bold: true } Item { @@ -220,8 +222,19 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight } + Item { + // for adding extra space between sections + Layout.fillWidth: true + } + + Item { + // for adding extra space between sections + Layout.fillWidth: true + } + MatrixText { text: "INFO" + font.bold: true } Item { @@ -234,7 +247,7 @@ ApplicationWindow { MatrixText { text: roomSettings.roomId - font.pixelSize: 12 + font.pixelSize: 14 Layout.alignment: Qt.AlignRight } @@ -244,7 +257,7 @@ ApplicationWindow { MatrixText { text: roomSettings.roomVersion - font.pixelSize: 12 + font.pixelSize: 14 Layout.alignment: Qt.AlignRight } } diff --git a/resources/qml/ToggleButton.qml b/resources/qml/ToggleButton.qml index 59ec1a12..dfef6207 100644 --- a/resources/qml/ToggleButton.qml +++ b/resources/qml/ToggleButton.qml @@ -5,8 +5,10 @@ import im.nheko 1.0 Switch { id: toggleButton + implicitWidth: indicatorItem.width indicator: Item { + id: indicatorItem implicitWidth: 48 implicitHeight: 24 y: parent.height / 2 - height / 2 From 8aadde7885031f34c309daefe203eeb94baa6983 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Wed, 17 Feb 2021 19:26:19 +0530 Subject: [PATCH 17/45] add matrix link for completed item --- src/RoomsModel.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp index f79c5515..6ace8ef7 100644 --- a/src/RoomsModel.cpp +++ b/src/RoomsModel.cpp @@ -37,7 +37,7 @@ RoomsModel::data(const QModelIndex &index, int role) const if (hasIndex(index.row(), index.column(), index.parent())) { switch (role) { case CompletionModel::CompletionRole: - return QString("%1").arg(roomAliases[index.row()]); + return QString("[%1](https://matrix.to/%1)").arg(roomAliases[index.row()]); case CompletionModel::SearchRole: case Qt::DisplayRole: case Roles::RoomAlias: From 0b6c82dfffd20a264dd54ee838a33a7e157adb59 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Wed, 17 Feb 2021 19:51:35 +0530 Subject: [PATCH 18/45] added bool to choose between showing only rooms with aliases and all of the rooms --- src/RoomsModel.cpp | 23 +++++++++++++++-------- src/RoomsModel.h | 6 +++--- src/timeline/InputBar.cpp | 4 ++-- 3 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp index 6ace8ef7..213d84df 100644 --- a/src/RoomsModel.cpp +++ b/src/RoomsModel.cpp @@ -3,18 +3,26 @@ #include "Cache_p.h" #include "CompletionModelRoles.h" -RoomsModel::RoomsModel(QObject *parent) +RoomsModel::RoomsModel(bool showOnlyRoomWithAliases, QObject *parent) : QAbstractListModel(parent) + , showOnlyRoomWithAliases_(showOnlyRoomWithAliases) { - rooms_ = cache::joinedRooms(); - roomInfos = cache::getRoomInfo(rooms_); + std::vector rooms_ = cache::joinedRooms(); + roomInfos = cache::getRoomInfo(rooms_); for (const auto &r : rooms_) { auto roomAliasesList = cache::client()->getRoomAliases(r); - if (roomAliasesList) { - roomAliases.push_back(QString::fromStdString(roomAliasesList->alias)); + if (showOnlyRoomWithAliases_) { + if (roomAliasesList) { + roomids.push_back(QString::fromStdString(r)); + roomAliases.push_back( + QString::fromStdString(roomAliasesList->alias)); + } + } else { roomids.push_back(QString::fromStdString(r)); + roomAliases.push_back( + roomAliasesList ? QString::fromStdString(roomAliasesList->alias) : ""); } } } @@ -43,14 +51,13 @@ RoomsModel::data(const QModelIndex &index, int role) const case Roles::RoomAlias: return roomAliases[index.row()]; case CompletionModel::SearchRole2: - return roomAliases[index.row()]; + case Roles::RoomName: + return QString::fromStdString(roomInfos.at(roomids[index.row()]).name); case Roles::AvatarUrl: return QString::fromStdString( roomInfos.at(roomids[index.row()]).avatar_url); case Roles::RoomID: return roomids[index.row()]; - case Roles::RoomName: - return QString::fromStdString(roomInfos.at(roomids[index.row()]).name); } } return {}; diff --git a/src/RoomsModel.h b/src/RoomsModel.h index 88cb5c68..0e006448 100644 --- a/src/RoomsModel.h +++ b/src/RoomsModel.h @@ -16,18 +16,18 @@ public: RoomName, }; - RoomsModel(QObject *parent = nullptr); + RoomsModel(bool showOnlyRoomWithAliases = false, QObject *parent = nullptr); QHash roleNames() const override; int rowCount(const QModelIndex &parent = QModelIndex()) const override { (void)parent; - return (int)roomAliases.size(); + return (int)roomids.size(); } QVariant data(const QModelIndex &index, int role) const override; private: - std::vector rooms_; std::vector roomids; std::vector roomAliases; std::map roomInfos; + bool showOnlyRoomWithAliases_; }; diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 49fa5249..5ef38ac7 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -19,11 +19,11 @@ #include "MainWindow.h" #include "MatrixClient.h" #include "Olm.h" +#include "RoomsModel.h" #include "TimelineModel.h" #include "TimelineViewManager.h" #include "UserSettingsPage.h" #include "UsersModel.h" -#include "RoomsModel.h" #include "Utils.h" #include "dialogs/PreviewUploadOverlay.h" #include "emoji/EmojiModel.h" @@ -188,7 +188,7 @@ InputBar::completerFor(QString completerName) emojiModel->setParent(proxy); return proxy; } else if (completerName == "room") { - auto roomModel = new RoomsModel(); + auto roomModel = new RoomsModel(true); auto proxy = new CompletionProxyModel(roomModel); roomModel->setParent(proxy); return proxy; From c9393fe3f658aada569c04427244786d280805a3 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Feb 2021 18:47:37 +0100 Subject: [PATCH 19/45] Fix crash from logging unset indices (leftover after debugging) --- src/timeline/TimelineModel.cpp | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 5c904932..3c33692e 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -753,11 +753,6 @@ TimelineModel::setCurrentIndex(int index) (!oldReadIndex || *oldReadIndex < nextEventIndexAndId->first)) { readEvent(nextEventIndexAndId->second); currentReadId = QString::fromStdString(nextEventIndexAndId->second); - - nhlog::net()->info("Marked as read {}, index {}, oldReadIndex {}", - nextEventIndexAndId->second, - nextEventIndexAndId->first, - *oldReadIndex); } } } From b8c6c716be9e03376bede0a734dbb815a5e92b93 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Feb 2021 22:14:19 +0100 Subject: [PATCH 20/45] Make inline images work a bit better --- src/timeline/TimelineModel.cpp | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 3c33692e..80e73440 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -362,6 +362,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r const static QRegularExpression replyFallback( ".*", QRegularExpression::DotMatchesEverythingOption); + auto ascent = QFontMetrics(UserSettings::instance()->font()).ascent(); + bool isReply = relations(event).reply_to().has_value(); auto formattedBody_ = QString::fromStdString(formatted_body(event)); @@ -380,8 +382,14 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r formattedBody_ = formattedBody_.remove(replyFallback); } - formattedBody_.replace("]*)src=\"mxc://([^\"]*)\"([^>]*>)"); + formattedBody_.replace(matchImgUri, "\\1 src=\"image://mxcImage/\\2\"\\3"); + const static QRegularExpression matchEmoticonHeight( + "(]*)height=\"([^\"]*)\"([^>]*>)"); + formattedBody_.replace(matchEmoticonHeight, + QString("\\1 height=\"%1\"\\3").arg(ascent)); return QVariant(utils::replaceEmoji( utils::linkifyMessage(utils::escapeBlacklistedHtml(formattedBody_)))); From 9f7dc5488ec4a632818f5661797f721c41808bb5 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 17 Feb 2021 23:45:41 +0100 Subject: [PATCH 21/45] Adapt to changes in MSC2312 --- src/ChatPage.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 45802789..9c814bd1 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -1326,14 +1326,14 @@ mxidFromSegments(QStringRef sigil, QStringRef mxid) auto mxid_ = QUrl::fromPercentEncoding(mxid.toUtf8()); - if (sigil == "user") { + if (sigil == "u") { return "@" + mxid_; } else if (sigil == "roomid") { return "!" + mxid_; - } else if (sigil == "room") { + } else if (sigil == "r") { return "#" + mxid_; - } else if (sigil == "group") { - return "+" + mxid_; + //} else if (sigil == "group") { + // return "+" + mxid_; } else { return ""; } @@ -1362,7 +1362,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) return; QString mxid2; - if (segments.size() == 4 && segments[2] == "event") { + if (segments.size() == 4 && segments[2] == "e") { if (segments[3].isEmpty()) return; else @@ -1383,7 +1383,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) } } - if (sigil1 == "user") { + if (sigil1 == "u") { if (action.isEmpty()) { view_manager_->activeTimeline()->openUserProfile(mxid1); } else if (action == "chat") { @@ -1403,7 +1403,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) if (action == "join") { joinRoomVia(targetRoomId, vias); } - } else if (sigil1 == "room") { + } else if (sigil1 == "r") { auto joined_rooms = cache::joinedRooms(); auto targetRoomAlias = mxid1.toStdString(); From 3ea0e79a36b72878a938a8c41b4c1271668fd429 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Fri, 19 Feb 2021 17:04:31 +0530 Subject: [PATCH 22/45] check for empty alias and percent encoding for alias in url --- src/RoomsModel.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/RoomsModel.cpp b/src/RoomsModel.cpp index 213d84df..4286f87b 100644 --- a/src/RoomsModel.cpp +++ b/src/RoomsModel.cpp @@ -1,5 +1,7 @@ #include "RoomsModel.h" +#include + #include "Cache_p.h" #include "CompletionModelRoles.h" @@ -14,7 +16,7 @@ RoomsModel::RoomsModel(bool showOnlyRoomWithAliases, QObject *parent) auto roomAliasesList = cache::client()->getRoomAliases(r); if (showOnlyRoomWithAliases_) { - if (roomAliasesList) { + if (roomAliasesList && !roomAliasesList->alias.empty()) { roomids.push_back(QString::fromStdString(r)); roomAliases.push_back( QString::fromStdString(roomAliasesList->alias)); @@ -44,8 +46,11 @@ RoomsModel::data(const QModelIndex &index, int role) const { if (hasIndex(index.row(), index.column(), index.parent())) { switch (role) { - case CompletionModel::CompletionRole: - return QString("[%1](https://matrix.to/%1)").arg(roomAliases[index.row()]); + case CompletionModel::CompletionRole: { + QString percentEncoding = QUrl::toPercentEncoding(roomAliases[index.row()]); + return QString("[%1](https://matrix.to/#/%2)") + .arg(roomAliases[index.row()], percentEncoding); + } case CompletionModel::SearchRole: case Qt::DisplayRole: case Roles::RoomAlias: From ebd12a6f33fa2cff04da1756b1363a3f4ec45f90 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Fri, 19 Feb 2021 15:48:43 +0100 Subject: [PATCH 23/45] Fix login with SSO and Password supported --- src/LoginPage.cpp | 47 +++++++++++++++++++++++++++++------------------ src/LoginPage.h | 11 ++++++----- 2 files changed, 35 insertions(+), 23 deletions(-) diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index 15aeb12a..26a170c5 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -147,16 +147,23 @@ LoginPage::LoginPage(QWidget *parent) error_matrixid_label_->hide(); button_layout_ = new QHBoxLayout(); - button_layout_->setSpacing(0); + button_layout_->setSpacing(20); button_layout_->setContentsMargins(0, 0, 0, 30); login_button_ = new RaisedButton(tr("LOGIN"), this); - login_button_->setMinimumSize(350, 65); + login_button_->setMinimumSize(150, 65); login_button_->setFontSize(20); login_button_->setCornerRadius(3); + sso_login_button_ = new RaisedButton(tr("SSO LOGIN"), this); + sso_login_button_->setMinimumSize(150, 65); + sso_login_button_->setFontSize(20); + sso_login_button_->setCornerRadius(3); + sso_login_button_->setVisible(false); + button_layout_->addStretch(1); button_layout_->addWidget(login_button_); + button_layout_->addWidget(sso_login_button_); button_layout_->addStretch(1); error_label_ = new QLabel(this); @@ -179,7 +186,12 @@ LoginPage::LoginPage(QWidget *parent) this, &LoginPage::versionErrorCb, this, &LoginPage::versionError, Qt::QueuedConnection); connect(back_button_, SIGNAL(clicked()), this, SLOT(onBackButtonClicked())); - connect(login_button_, SIGNAL(clicked()), this, SLOT(onLoginButtonClicked())); + connect(login_button_, &RaisedButton::clicked, this, [this]() { + onLoginButtonClicked(passwordSupported ? LoginMethod::Password : LoginMethod::SSO); + }); + connect(sso_login_button_, &RaisedButton::clicked, this, [this]() { + onLoginButtonClicked(LoginMethod::SSO); + }); connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click())); @@ -314,16 +326,19 @@ LoginPage::checkHomeserverVersion() http::client()->get_login( [this](mtx::responses::LoginFlows flows, mtx::http::RequestErr err) { if (err || flows.flows.empty()) - emit versionOkCb(LoginMethod::Password); + emit versionOkCb(true, false); - LoginMethod loginMethod_ = LoginMethod::Password; + bool ssoSupported_ = false; + bool passwordSupported_ = false; for (const auto &flow : flows.flows) { if (flow.type == mtx::user_interactive::auth_types::sso) { - loginMethod_ = LoginMethod::SSO; - break; + ssoSupported_ = true; + } else if (flow.type == + mtx::user_interactive::auth_types::password) { + passwordSupported_ = true; } } - emit versionOkCb(loginMethod_); + emit versionOkCb(passwordSupported_, ssoSupported_); }); }); } @@ -355,28 +370,24 @@ LoginPage::versionError(const QString &error) } void -LoginPage::versionOk(LoginMethod loginMethod_) +LoginPage::versionOk(bool passwordSupported_, bool ssoSupported_) { - this->loginMethod = loginMethod_; + passwordSupported = passwordSupported_; + ssoSupported = ssoSupported_; serverLayout_->removeWidget(spinner_); matrixidLayout_->removeWidget(spinner_); spinner_->stop(); - if (loginMethod == LoginMethod::SSO) { - password_input_->hide(); - login_button_->setText(tr("SSO LOGIN")); - } else { - password_input_->show(); - login_button_->setText(tr("LOGIN")); - } + sso_login_button_->setVisible(ssoSupported); + login_button_->setVisible(passwordSupported); if (serverInput_->isVisible()) serverInput_->hide(); } void -LoginPage::onLoginButtonClicked() +LoginPage::onLoginButtonClicked(LoginMethod loginMethod) { error_label_->setText(""); diff --git a/src/LoginPage.h b/src/LoginPage.h index 5ed21dec..2341c0ce 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -56,7 +56,7 @@ signals: //! Used to trigger the corresponding slot outside of the main thread. void versionErrorCb(const QString &err); - void versionOkCb(LoginPage::LoginMethod method); + void versionOkCb(bool passwordSupported, bool ssoSupported); void loginOk(const mtx::responses::Login &res); @@ -73,7 +73,7 @@ private slots: void onBackButtonClicked(); // Callback for the login button. - void onLoginButtonClicked(); + void onLoginButtonClicked(LoginMethod loginMethod); // Callback for probing the server found in the mxid void onMatrixIdEntered(); @@ -84,7 +84,7 @@ private slots: // Callback for errors produced during server probing void versionError(const QString &error_message); // Callback for successful server probing - void versionOk(LoginPage::LoginMethod method); + void versionOk(bool passwordSupported, bool ssoSupported); private: void checkHomeserverVersion(); @@ -120,7 +120,7 @@ private: QString inferredServerAddress_; FlatButton *back_button_; - RaisedButton *login_button_; + RaisedButton *login_button_, *sso_login_button_; QWidget *form_widget_; QHBoxLayout *form_wrapper_; @@ -130,5 +130,6 @@ private: TextField *password_input_; TextField *deviceName_; TextField *serverInput_; - LoginMethod loginMethod = LoginMethod::Password; + bool passwordSupported = true; + bool ssoSupported = false; }; From 264a85b9e41321ad61f0adf5efa0cd0faa2cf263 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 20 Feb 2021 02:38:41 +0100 Subject: [PATCH 24/45] Avoid some copies when sorting the room list --- src/RoomInfoListItem.h | 2 ++ src/RoomList.cpp | 8 ++++---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/RoomInfoListItem.h b/src/RoomInfoListItem.h index baa8b98b..c2826f6f 100644 --- a/src/RoomInfoListItem.h +++ b/src/RoomInfoListItem.h @@ -217,4 +217,6 @@ private: QColor bubbleBgColor_; QColor bubbleFgColor_; + + friend struct room_sort; }; diff --git a/src/RoomList.cpp b/src/RoomList.cpp index 67a7ac40..10042c94 100644 --- a/src/RoomList.cpp +++ b/src/RoomList.cpp @@ -353,8 +353,8 @@ RoomList::updateRoomDescription(const QString &roomid, const DescInfo &info) struct room_sort { - bool operator()(const QSharedPointer a, - const QSharedPointer b) const + bool operator()(const QSharedPointer &a, + const QSharedPointer &b) const { // Sort by "importance" (i.e. invites before mentions before // notifs before new events before old events), then secondly @@ -370,9 +370,9 @@ struct room_sort // Now sort by recency // Zero if empty, otherwise the time that the event occured const uint64_t a_recency = - a->lastMessageInfo().userid.isEmpty() ? 0 : a->lastMessageInfo().timestamp; + a->lastMsgInfo_.userid.isEmpty() ? 0 : a->lastMsgInfo_.timestamp; const uint64_t b_recency = - b->lastMessageInfo().userid.isEmpty() ? 0 : b->lastMessageInfo().timestamp; + b->lastMsgInfo_.userid.isEmpty() ? 0 : b->lastMsgInfo_.timestamp; return a_recency > b_recency; } }; From 8351cc41802c7f2de1085ae4196da5a0733d72c8 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 20 Feb 2021 02:53:14 +0100 Subject: [PATCH 25/45] Fix miscalculation of padding in timeline --- resources/qml/MessageView.qml | 2 +- resources/qml/RoomSettings.qml | 68 ++++++++++++++++++---------------- resources/qml/ToggleButton.qml | 16 +++++--- resources/qml/UserProfile.qml | 1 + 4 files changed, 48 insertions(+), 39 deletions(-) diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index e1641a36..eefde046 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -15,7 +15,7 @@ ScrollView { ListView { id: chat - property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding + property int delegateMaxWidth: ((Settings.timelineMaxWidth > 100 && Settings.timelineMaxWidth < parent.availableWidth) ? Settings.timelineMaxWidth : parent.availableWidth) - parent.padding * 2 model: TimelineManager.timeline boundsBehavior: Flickable.StopAtBounds diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 898853e9..b37dab76 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -1,16 +1,16 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 +import QtQuick.Dialogs 1.2 import QtQuick.Layouts 1.2 import QtQuick.Window 2.3 -import QtQuick.Dialogs 1.2 import im.nheko 1.0 ApplicationWindow { - id: roomSettingsDialog + id: roomSettingsDialog property var roomSettings - x: MainWindow.x + (MainWindow.width / 2) - (width / 2) + x: MainWindow.x + (MainWindow.width / 2) - (width / 2) y: MainWindow.y + (MainWindow.height / 2) - (height / 2) minimumWidth: 420 minimumHeight: 650 @@ -36,9 +36,9 @@ ApplicationWindow { width: 130 Layout.alignment: Qt.AlignHCenter onClicked: { - if(roomSettings.canChangeAvatar) { - roomSettings.updateAvatar(); - } + if (roomSettings.canChangeAvatar) + roomSettings.updateAvatar(); + } } @@ -50,6 +50,7 @@ ApplicationWindow { Text { id: errorText + text: "Error Text" color: "red" visible: opacity > 0 @@ -59,24 +60,28 @@ ApplicationWindow { SequentialAnimation { id: hideErrorAnimation + running: false + PauseAnimation { duration: 4000 } + NumberAnimation { target: errorText property: 'opacity' to: 0 duration: 1000 } + } - Connections{ + Connections { target: roomSettings onDisplayError: { - errorText.text = errorMessage - errorText.opacity = 1 - hideErrorAnimation.restart() + errorText.text = errorMessage; + errorText.opacity = 1; + hideErrorAnimation.restart(); } } @@ -93,6 +98,7 @@ ApplicationWindow { text: "%1 member(s)".arg(roomSettings.memberCount) Layout.alignment: Qt.AlignHCenter } + } ImageButton { @@ -117,6 +123,7 @@ ApplicationWindow { color: colors.text horizontalAlignment: TextEdit.AlignHCenter } + } GridLayout { @@ -138,10 +145,10 @@ ApplicationWindow { } ComboBox { - model: [ "Muted", "Mentions only", "All messages" ] + model: ["Muted", "Mentions only", "All messages"] currentIndex: roomSettings.notifications onActivated: { - roomSettings.changeNotifications(index) + roomSettings.changeNotifications(index); } Layout.fillWidth: true } @@ -153,10 +160,10 @@ ApplicationWindow { ComboBox { enabled: roomSettings.canChangeJoinRules - model: [ "Anyone and guests", "Anyone", "Invited users" ] + model: ["Anyone and guests", "Anyone", "Invited users"] currentIndex: roomSettings.accessJoinRules onActivated: { - roomSettings.changeAccessRules(index) + roomSettings.changeAccessRules(index); } Layout.fillWidth: true } @@ -170,11 +177,10 @@ ApplicationWindow { checked: roomSettings.isEncryptionEnabled onClicked: { - if(roomSettings.isEncryptionEnabled) { - checked=true; - return; + if (roomSettings.isEncryptionEnabled) { + checked = true; + return ; } - confirmEncryptionDialog.open(); } Layout.alignment: Qt.AlignRight @@ -182,24 +188,21 @@ ApplicationWindow { MessageDialog { id: confirmEncryptionDialog + title: qsTr("End-to-End Encryption") - text: qsTr("Encryption is currently experimental and things might break unexpectedly.
+ text: qsTr("Encryption is currently experimental and things might break unexpectedly.
Please take note that it can't be disabled afterwards.") modality: Qt.WindowModal icon: StandardIcon.Question - onAccepted: { - if(roomSettings.isEncryptionEnabled) { - return; - } + if (roomSettings.isEncryptionEnabled) + return ; roomSettings.enableEncryption(); } - onRejected: { - encryptionToggle.checked = false + encryptionToggle.checked = false; } - standardButtons: Dialog.Ok | Dialog.Cancel } @@ -210,14 +213,12 @@ ApplicationWindow { ToggleButton { visible: roomSettings.isEncryptionEnabled - ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys - upon request. Use with caution, this is a temporary measure to test the + ToolTip.text: qsTr("Whether or not the client should respond automatically with the session keys + upon request. Use with caution, this is a temporary measure to test the E2E implementation until device verification is completed.") - checked: roomSettings.respondsToKeyRequests - onClicked: { - roomSettings.changeKeyRequestsPreference(checked) + roomSettings.changeKeyRequestsPreference(checked); } Layout.alignment: Qt.AlignRight } @@ -260,6 +261,7 @@ ApplicationWindow { font.pixelSize: 14 Layout.alignment: Qt.AlignRight } + } Button { @@ -267,5 +269,7 @@ ApplicationWindow { text: "Ok" onClicked: close() } + } -} \ No newline at end of file + +} diff --git a/resources/qml/ToggleButton.qml b/resources/qml/ToggleButton.qml index dfef6207..9f079c62 100644 --- a/resources/qml/ToggleButton.qml +++ b/resources/qml/ToggleButton.qml @@ -5,32 +5,36 @@ import im.nheko 1.0 Switch { id: toggleButton + implicitWidth: indicatorItem.width - indicator: Item { + indicator: Item { id: indicatorItem + implicitWidth: 48 implicitHeight: 24 y: parent.height / 2 - height / 2 Rectangle { - height: 3 * parent.height/4 - radius: height/2 + height: 3 * parent.height / 4 + radius: height / 2 width: parent.width - height x: radius y: parent.height / 2 - height / 2 color: toggleButton.checked ? "skyblue" : "grey" border.color: "#cccccc" } - + Rectangle { x: toggleButton.checked ? parent.width - width : 0 y: parent.height / 2 - height / 2 width: parent.height height: width - radius: width/2 + radius: width / 2 color: toggleButton.down ? "whitesmoke" : "whitesmoke" border.color: "#ebebeb" } + } -} \ No newline at end of file + +} diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 003f6b3a..4797a38e 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -118,6 +118,7 @@ ApplicationWindow { } } } + } MatrixText { From af9b66dd3ece30e3870acc19e3e34cc70a9eaf68 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Mon, 22 Feb 2021 21:35:11 +0100 Subject: [PATCH 26/45] Linkify topic in room settings and use non-deprecated MessageDialog --- README.md | 4 ++-- resources/qml/RoomSettings.qml | 24 +++++++++++++++--------- src/ui/RoomSettings.cpp | 4 ++-- 3 files changed, 19 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index 4f8aa111..15d4f416 100644 --- a/README.md +++ b/README.md @@ -191,7 +191,7 @@ sudo emerge -a ">=dev-qt/qtgui-5.10.0" media-libs/fontconfig dev-libs/qtkeychain ```bash # Build requirements + qml modules needed at runtime (you may not need all of them, but the following seem to work according to reports): -sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,graphicaleffects,quick-controls2} qt5keychain-dev +sudo apt install g++ cmake zlib1g-dev libssl-dev qt{base,declarative,tools,multimedia,quickcontrols2-}5-dev libqt5svg5-dev libboost-system-dev libboost-thread-dev libboost-iostreams-dev libolm-dev liblmdb++-dev libcmark-dev nlohmann-json3-dev libspdlog-dev libgtest-dev qml-module-qt{gstreamer,multimedia,quick-extras,-labs-settings,-labs-platform,graphicaleffects,quick-controls2} qt5keychain-dev ``` This will install all dependencies, except for tweeny (use bundled tweeny) and mtxclient (needs to be build separately). @@ -204,7 +204,7 @@ and mtxclient (needs to be build separately). sudo apt install cmake gcc make automake liblmdb-dev \ qt5-default libssl-dev libqt5multimedia5-plugins libqt5multimediagsttools5 libqt5multimediaquick5 libqt5svg5-dev \ qml-module-qtgstreamer qtmultimedia5-dev qtquickcontrols2-5-dev qttools5-dev qttools5-dev-tools qtdeclarative5-dev \ - qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts \ + qml-module-qtgraphicaleffects qml-module-qtmultimedia qml-module-qtquick-controls2 qml-module-qtquick-layouts qml-module-qt-labs-platform\ qt5keychain-dev ``` diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index b37dab76..58e34a8f 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -1,6 +1,6 @@ import QtQuick 2.9 import QtQuick.Controls 2.3 -import QtQuick.Dialogs 1.2 +import Qt.labs.platform 1.1 as Platform import QtQuick.Layouts 1.2 import QtQuick.Window 2.3 import im.nheko 1.0 @@ -110,18 +110,25 @@ ApplicationWindow { ScrollView { Layout.maximumHeight: 75 - ScrollBar.horizontal.policy: ScrollBar.AlwaysOff Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true + width: parent.width TextArea { - text: roomSettings.roomTopic - wrapMode: TextEdit.WordWrap + text: TimelineManager.escapeEmoji(roomSettings.roomTopic) + wrapMode: TextEdit.WordWrap + textFormat: TextEdit.RichText readOnly: true background: null selectByMouse: true color: colors.text - horizontalAlignment: TextEdit.AlignHCenter + horizontalAlignment: TextEdit.AlignHCenter + + onLinkActivated: TimelineManager.openLink(link); + + CursorShape { + anchors.fill: parent + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } } } @@ -186,14 +193,13 @@ ApplicationWindow { Layout.alignment: Qt.AlignRight } - MessageDialog { + Platform.MessageDialog { id: confirmEncryptionDialog title: qsTr("End-to-End Encryption") text: qsTr("Encryption is currently experimental and things might break unexpectedly.
Please take note that it can't be disabled afterwards.") modality: Qt.WindowModal - icon: StandardIcon.Question onAccepted: { if (roomSettings.isEncryptionEnabled) return ; @@ -203,7 +209,7 @@ ApplicationWindow { onRejected: { encryptionToggle.checked = false; } - standardButtons: Dialog.Ok | Dialog.Cancel + buttons: Dialog.Ok | Dialog.Cancel } MatrixText { diff --git a/src/ui/RoomSettings.cpp b/src/ui/RoomSettings.cpp index aa6f60a0..a264c78b 100644 --- a/src/ui/RoomSettings.cpp +++ b/src/ui/RoomSettings.cpp @@ -229,7 +229,7 @@ RoomSettings::roomName() const QString RoomSettings::roomTopic() const { - return QString::fromStdString(info_.topic); + return utils::linkifyMessage(QString::fromStdString(info_.topic).toHtmlEscaped()); } QString @@ -622,4 +622,4 @@ RoomSettings::updateAvatar() emit proxy->stopLoading(); }); }); -} \ No newline at end of file +} From 78ecffb45b8493f07bc91042a706227e22e3788e Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sun, 21 Feb 2021 02:11:50 +0100 Subject: [PATCH 27/45] Use scrollview again for input --- resources/qml/MessageInput.qml | 55 +++++++++------------------------- 1 file changed, 14 insertions(+), 41 deletions(-) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 7ecaf81a..c855ef90 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -1,14 +1,16 @@ import "./voip" -import QtQuick 2.9 +import QtQuick 2.12 import QtQuick.Controls 2.3 import QtQuick.Layouts 1.2 import QtQuick.Window 2.2 import im.nheko 1.0 Rectangle { + id: inputBar + color: colors.window Layout.fillWidth: true - Layout.preferredHeight: textInput.height + 16 + Layout.preferredHeight: row.implicitHeight Layout.minimumHeight: 40 Component { @@ -20,11 +22,9 @@ Rectangle { } RowLayout { - id: inputBar + id: row anchors.fill: parent - anchors.margins: 8 - spacing: 16 ImageButton { visible: CallManager.callsSupported @@ -36,7 +36,7 @@ Rectangle { image: CallManager.isOnCall ? ":/icons/icons/ui/end-call.png" : ":/icons/icons/ui/place-call.png" ToolTip.visible: hovered ToolTip.text: CallManager.isOnCall ? qsTr("Hang up") : qsTr("Place a call") - Layout.leftMargin: 8 + Layout.margins: 8 onClicked: { if (TimelineManager.timeline) { if (CallManager.haveCallInvite) { @@ -58,7 +58,7 @@ Rectangle { width: 22 height: 22 image: ":/icons/icons/ui/paper-clip-outline.png" - Layout.leftMargin: CallManager.callsSupported ? 0 : 8 + Layout.margins: 8 onClicked: TimelineManager.timeline.input.openFileSelection() ToolTip.visible: hovered ToolTip.text: qsTr("Send a file") @@ -77,31 +77,13 @@ Rectangle { } - Flickable { + ScrollView { id: textInput - function ensureVisible(r) { - if (contentX >= r.x) - contentX = r.x; - else if (contentX + width <= r.x + r.width) - contentX = r.x + r.width - width; - if (contentY >= r.y) - contentY = r.y; - else if (contentY + height <= r.y + r.height) - contentY = r.y + r.height - height; - } - - Layout.alignment: Qt.AlignBottom + Layout.alignment: Qt.AlignBottom // | Qt.AlignHCenter Layout.maximumHeight: Window.height / 4 Layout.minimumHeight: Settings.fontSize - Layout.fillWidth: true - clip: true - boundsBehavior: Flickable.StopAtBounds - flickableDirection: Flickable.VerticalFlick - implicitWidth: messageInput.width - implicitHeight: messageInput.height - contentWidth: messageInput.width - contentHeight: messageInput.height + implicitWidth: inputBar.width - 4 * (22 + 16) - 24 TextArea { id: messageInput @@ -122,18 +104,11 @@ Rectangle { selectByMouse: true placeholderText: qsTr("Write a message...") - //placeholderTextColor: colors.buttonText - // only set the anchors on Qt 5.12 or higher - // see https://doc.qt.io/qt-5/qml-qtquick-controls2-popup.html#anchors.centerIn-prop - Component.onCompleted: { - if (placeholderTextColor !== undefined) - placeholderTextColor = colors.buttonText; - - } + placeholderTextColor: colors.buttonText color: colors.text width: textInput.width wrapMode: TextEdit.Wrap - padding: 0 + padding: 8 focus: true onTextChanged: { if (TimelineManager.timeline) @@ -141,7 +116,6 @@ Rectangle { forceActiveFocus(); } - onCursorRectangleChanged: textInput.ensureVisible(cursorRectangle) onCursorPositionChanged: { if (!TimelineManager.timeline) return ; @@ -296,15 +270,13 @@ Rectangle { } - ScrollBar.vertical: ScrollBar { - } - } ImageButton { id: emojiButton Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.margins: 8 hoverEnabled: true width: 22 height: 22 @@ -319,6 +291,7 @@ Rectangle { ImageButton { Layout.alignment: Qt.AlignRight | Qt.AlignBottom + Layout.margins: 8 hoverEnabled: true width: 22 height: 22 From 7560972cacec20f90af492b53de3a132aee65793 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 23 Feb 2021 05:24:34 +0100 Subject: [PATCH 28/45] Fix qml formatting --- resources/qml/RoomSettings.qml | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/resources/qml/RoomSettings.qml b/resources/qml/RoomSettings.qml index 58e34a8f..1165f472 100644 --- a/resources/qml/RoomSettings.qml +++ b/resources/qml/RoomSettings.qml @@ -1,6 +1,6 @@ +import Qt.labs.platform 1.1 as Platform import QtQuick 2.9 import QtQuick.Controls 2.3 -import Qt.labs.platform 1.1 as Platform import QtQuick.Layouts 1.2 import QtQuick.Window 2.3 import im.nheko 1.0 @@ -115,20 +115,20 @@ ApplicationWindow { TextArea { text: TimelineManager.escapeEmoji(roomSettings.roomTopic) - wrapMode: TextEdit.WordWrap - textFormat: TextEdit.RichText + wrapMode: TextEdit.WordWrap + textFormat: TextEdit.RichText readOnly: true background: null selectByMouse: true color: colors.text - horizontalAlignment: TextEdit.AlignHCenter + horizontalAlignment: TextEdit.AlignHCenter + onLinkActivated: TimelineManager.openLink(link) - onLinkActivated: TimelineManager.openLink(link); + CursorShape { + anchors.fill: parent + cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor + } - CursorShape { - anchors.fill: parent - cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.ArrowCursor - } } } From 865344c7aa6e58c471956f44a60099c902495758 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 23 Feb 2021 12:42:57 +0100 Subject: [PATCH 29/45] Fix unused capture --- src/notifications/ManagerLinux.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/notifications/ManagerLinux.cpp b/src/notifications/ManagerLinux.cpp index fb424b2a..c7fd4023 100644 --- a/src/notifications/ManagerLinux.cpp +++ b/src/notifications/ManagerLinux.cpp @@ -109,7 +109,7 @@ NotificationsManager::closeNotification(uint id) "org.freedesktop.Notifications"); auto call = closeCall.asyncCall("CloseNotification", (uint)id); // replace_id auto watcher = new QDBusPendingCallWatcher{call, this}; - connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher, this]() { + connect(watcher, &QDBusPendingCallWatcher::finished, this, [watcher]() { if (watcher->reply().type() == QDBusMessage::ErrorMessage) { qDebug() << "D-Bus Error:" << watcher->reply().errorMessage(); }; From 28fd47bf8d6835ede9cf94ad87f73787397cac20 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 23 Feb 2021 19:26:26 +0100 Subject: [PATCH 30/45] Add flatpak nightly repository --- .gitlab-ci.yml | 6 +- README.md | 1 + nheko-nightly.flatpakref | 10 + scripts/flat-manager-client | 766 ++++++++++++++++++++++++++++++ scripts/upload-to-flatpak-repo.sh | 23 + 5 files changed, 804 insertions(+), 2 deletions(-) create mode 100644 nheko-nightly.flatpakref create mode 100755 scripts/flat-manager-client create mode 100755 scripts/upload-to-flatpak-repo.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index f03dcbbe..b44e740b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -88,7 +88,7 @@ build-flatpak-amd64: #image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master' tags: [docker] before_script: - - apt-get update && apt-get -y install flatpak-builder git python curl + - apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak --noninteractive install --user flathub org.kde.Platform//5.15 - flatpak --noninteractive install --user flathub org.kde.Sdk//5.15 @@ -99,6 +99,7 @@ build-flatpak-amd64: - flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date`" app ../io.github.NhekoReborn.Nheko.json - flatpak build-bundle repo nheko-amd64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_} after_script: + - (cd ./scripts && ./upload-to-flatpak-repo.sh ../build-flatpak/repo) || true - bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-amd64.flatpak cache: key: "$CI_JOB_NAME" @@ -115,7 +116,7 @@ build-flatpak-arm64: #image: 'registry.gitlab.gnome.org/gnome/gnome-runtime-images/gnome:master' tags: [docker-arm64] before_script: - - apt-get update && apt-get -y install flatpak-builder git python curl + - apt-get update && apt-get -y install flatpak-builder git python curl python3-aiohttp python3-tenacity gir1.2-ostree-1.0 - flatpak remote-add --user --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo - flatpak --noninteractive install --user flathub org.kde.Platform//5.15 - flatpak --noninteractive install --user flathub org.kde.Sdk//5.15 @@ -126,6 +127,7 @@ build-flatpak-arm64: - flatpak-builder --user --disable-rofiles-fuse --ccache --repo=repo --default-branch=${CI_COMMIT_REF_NAME//\//_} --subject="Build of Nheko ${VERSION} `date` for arm64" app ../io.github.NhekoReborn.Nheko.json - flatpak build-bundle repo nheko-arm64.flatpak io.github.NhekoReborn.Nheko ${CI_COMMIT_REF_NAME//\//_} after_script: + - (cd ./scripts && ./upload-to-flatpak-repo.sh ../build-flatpak/repo) || true - bash ./.ci/upload-nightly-gitlab.sh build-flatpak/nheko-arm64.flatpak cache: key: "$CI_JOB_NAME" diff --git a/README.md b/README.md index 15d4f416..4bcd7254 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,7 @@ nheko [![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master) [![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.8.1) [![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://matrix-static.neko.dev/room/!TshDrgpBNBDmfDeEGN:neko.dev/) +[![Nightly Flatpak](https://img.shields.io/badge/download-nheko--nightly-green)](https://raw.githubusercontent.com/Nheko-Reborn/nheko/master/nheko-nightly.flatpakref) [![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org) [![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko) Download on Flathub diff --git a/nheko-nightly.flatpakref b/nheko-nightly.flatpakref new file mode 100644 index 00000000..7d27bdfe --- /dev/null +++ b/nheko-nightly.flatpakref @@ -0,0 +1,10 @@ +[Flatpak Ref] +Title=Nheko Nightly +Name=io.github.NhekoReborn.Nheko +Branch=master +Url=https://flatpak.neko.dev/repo/nightly +Homepage=https://nheko-reborn.github.io/ +Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg +RuntimeRepo=https://dl.flathub.org/repo/flathub.flatpakrepo +IsRuntime=false +GPGKey=mDMEXENMphYJKwYBBAHaRw8BAQdAqn+Eo42lPoGpJ5HaOf4nFGfxR0QtOggJTCfsdbOyL4e0Kk5pY29sYXMgV2VybmVyIDxuaWNvbGFzLndlcm5lckBob3RtYWlsLmRlPoiWBBMWCAA+FiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAlxDTVUCGwMFCQtJjooFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQkgauGyMeBbs2rQD/dAEoOGT21BL85A8LmPK743EboBAjoRbWcI1hHnvS28AA/3b3HYGwgvTC6hQLyz75zjpeO5ZaUtbezRyDUR4xabMAtCROaWNvbGFzIFdlcm5lciA8bmljb2xhc0BuZWtvZGV2Lm5ldD6IlgQTFggAPhYhBNWLRiQlpqNxJcb+25IGrhsjHgW7BQJcQ01GAhsDBQkLSY6KBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJIGrhsjHgW7GxwBANT4gL03Uu9N5KmTBihC7B71+0r7M/azPbUh86NthCeIAQCF2JXa0axBKhgQF5fWC5ncL+m8ZpH7C5rzDqVgO82WALQnTmljb2xhcyBXZXJuZXIgPG5pY29sYXMud2VybmVyQGdteC5uZXQ+iJYEExYIAD4WIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbAwUJC0mOigULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCSBq4bIx4FuxU5APoCRDYlJW0oTsJs3lcTTB5Nsqb3X4iCEDCjIgsA3wtsIwEAlGBzD8ElCYi2+8m8esSRNlmpRcGoqgXbceLxPUXFpQu4OARcQ0ymEgorBgEEAZdVAQUBAQdAD8dBmT3iqrqdlxSw90L0SIH11fVxiX9MdWfBkTi6PzUDAQgHiH4EGBYIACYWIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbDAUJC0mOigAKCRCSBq4bIx4Fu/LNAQDhH64IBic6h7H3uvtSAFT4xNn7Epobt2baIaDp7uKsQQEAyI+oc5dLknABwIOMrQQuZCmGejx9e4/8HEqLCdszhgG4MwRgNICHFgkrBgEEAdpHDwEBB0DR9eFFzfR62FIi7g+txcQenLvKFzhlyTz0wo3icOy6RYj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0gIcCGwIFCQlmAYAAgQkQkgauGyMeBbt2IAQZFggAHRYhBGz14re9h4cNPaFEKMjXXmEHc/LZBQJgNICHAAoJEMjXXmEHc/LZhVMBAPdYRspdeFh6E9BDxGubT705e/pZFdCHjCToDyxgdW5KAP9sU0hFI5VDHD1h98RzxSt7hc3jxyPSzbG1MBUJ9gbfCVhcAPsFfeZc3v5UBgmn4uICFEGjlzAWCQ7WctE6QTSkY5aL/wD9ETJH5lB+i/8km/sOBKQozXR0yHHw46gB6ZWMeN1wfgq4MwRgNPutFgkrBgEEAdpHDwEBB0APwMn0FJmnAds8IO8iCl/RHr7fz8xnpGd7E4zVgCNZpIj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0+60CGwIFCQANLwAAgQkQkgauGyMeBbt2IAQZFggAHRYhBAH7QBkzNfVIZJM93RNnXzGtBKQcBQJgNPutAAoJEBNnXzGtBKQcHnUA/0E2H5sxmfZ+EWFTso3X4NWu3uN2xF+MdNaY8C72f9H6AP91XaNmlB9gV61rg6wcB5E/j0998yWS9gltY1XY1ImqDPvlAP4sHFs5zuDazgKYxZ/kFhENCgEStdpnvJjt/DxmQPVT3AD/QK5vGoMTIeYjihv0QCnnRDfboTTZHlaEqJW8i02PQww= diff --git a/scripts/flat-manager-client b/scripts/flat-manager-client new file mode 100755 index 00000000..183ebf81 --- /dev/null +++ b/scripts/flat-manager-client @@ -0,0 +1,766 @@ +#!/usr/bin/python3 + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License along +# with this program; if not, write to the Free Software Foundation, Inc., +# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +import asyncio +import base64 +import binascii +import errno +import fnmatch +import gzip +import json +import logging +import os +import sys +import time +import traceback +from argparse import ArgumentParser +from functools import reduce +from urllib.parse import urljoin, urlparse, urlsplit, urlunparse, urlunsplit + +import aiohttp +from tenacity import retry, retry_if_exception_type, stop_after_attempt, wait_fixed + +import gi +gi.require_version('OSTree', '1.0') +from gi.repository import Gio, GLib, OSTree + +UPLOAD_CHUNK_LIMIT = 4 * 1024 * 1024 +DEFAULT_LIMIT = 2 ** 16 + +def eprint(*args, **kwargs): + print(*args, file=sys.stderr, **kwargs) + +class UsageException(Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return self.msg + +class ApiError(Exception): + def __init__(self, response, body): + self.url = response.url + self.status = response.status + + try: + self.body = json.loads(response); + except: + self.body = {"status": self.status, "error-type": "no-error", "message": "No json error details from server"} + + def repr(self): + return { + "type": "api", + "url": self.url, + "status_code": self.status, + "details": self.body + } + + def __str__(self): + return "Api call to %s failed with status %d, details: %s" % (self.url, self.status, self.body) + return json.dumps(self.repr(), indent=4) + +# This is similar to the regular payload, but opens the file lazily +class AsyncNamedFilePart(aiohttp.payload.Payload): + def __init__(self, + value, + disposition='attachment', + *args, + **kwargs): + self._file = None + if 'filename' not in kwargs: + kwargs['filename'] = os.path.basename(value) + + super().__init__(value, *args, **kwargs) + + if self._filename is not None and disposition is not None: + self.set_content_disposition(disposition, filename=self._filename, quote_fields=False) + + self._size = os.stat(value).st_size + + async def write(self, writer): + if self._file is None or self._file.closed: + self._file = open(self._value, 'rb') + try: + chunk = self._file.read(DEFAULT_LIMIT) + while chunk: + await writer.write(chunk) + chunk = self._file.read(DEFAULT_LIMIT) + finally: + self._file.close() + + @property + def size(self): + return self._size + +def ostree_object_path(repo, obj): + repodir = repo.get_path().get_path() + return os.path.join(repodir, 'objects', obj[0:2], obj[2:]) + +def ostree_get_dir_files(repo, objects, dirtree): + if dirtree.endswith(".dirtree"): + dirtree = dirtree[:-8] + dirtreev = repo.load_variant(OSTree.ObjectType.DIR_TREE, dirtree)[1] + iter = OSTree.RepoCommitTraverseIter() + iter.init_dirtree(repo, dirtreev, 0) + while True: + type = iter.next() + if type == OSTree.RepoCommitIterResult.END: + break + if type == OSTree.RepoCommitIterResult.ERROR: + break + if type == OSTree.RepoCommitIterResult.FILE: + d = iter.get_file() + objects.add(d.out_checksum + ".filez") + if type == OSTree.RepoCommitIterResult.DIR: + pass + +def local_needed_files(repo, metadata_objects): + objects = set() + for c in metadata_objects: + if c.endswith(".dirtree"): + ostree_get_dir_files(repo, objects, c) + return objects + +def local_needed_metadata_dirtree(repo, objects, dirtree_content, dirtree_meta): + objects.add(dirtree_meta + ".dirmeta") + dirtree_content_name = dirtree_content + ".dirtree" + if dirtree_content_name in objects: + return + objects.add(dirtree_content_name) + + dirtreev = repo.load_variant(OSTree.ObjectType.DIR_TREE, dirtree_content)[1] + iter = OSTree.RepoCommitTraverseIter() + iter.init_dirtree(repo, dirtreev, 0) + while True: + type = iter.next() + if type == OSTree.RepoCommitIterResult.END: + break + if type == OSTree.RepoCommitIterResult.ERROR: + break + if type == OSTree.RepoCommitIterResult.FILE: + pass + if type == OSTree.RepoCommitIterResult.DIR: + d = iter.get_dir() + local_needed_metadata_dirtree(repo, objects, d.out_content_checksum, d.out_meta_checksum) + +def local_needed_metadata(repo, commits): + objects = set() + for rev in commits: + objects.add(rev + ".commit") + commitv = repo.load_variant(OSTree.ObjectType.COMMIT, rev)[1] + iter = OSTree.RepoCommitTraverseIter() + iter.init_commit(repo, commitv, 0) + while True: + type = iter.next() + if type == OSTree.RepoCommitIterResult.END: + break + if type == OSTree.RepoCommitIterResult.ERROR: + break + if type == OSTree.RepoCommitIterResult.FILE: + pass + if type == OSTree.RepoCommitIterResult.DIR: + d = iter.get_dir() + local_needed_metadata_dirtree(repo, objects, d.out_content_checksum, d.out_meta_checksum) + return objects + + +def chunks(l, n): + """Yield successive n-sized chunks from l.""" + for i in range(0, len(l), n): + yield l[i:i + n] + +async def missing_objects(session, build_url, token, wanted): + missing=[] + for chunk in chunks(wanted, 2000): + wanted_json=json.dumps({'wanted': chunk}).encode('utf-8') + data=gzip.compress(wanted_json) + headers = { + 'Authorization': 'Bearer ' + token, + 'Content-Encoding': 'gzip', + 'Content-Type': 'application/json' + } + resp = await session.get(build_url + "/missing_objects", data=data, headers=headers) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + data = await resp.json() + missing.extend(data["missing"]) + return missing + +@retry( + stop=stop_after_attempt(6), + wait=wait_fixed(20), + retry=retry_if_exception_type(ApiError), + reraise=True, +) +async def upload_files(session, build_url, token, files): + if len(files) == 0: + return + print("Uploading %d files (%d bytes)" % (len(files), reduce(lambda x, y: x + y, map(lambda f: f.size, files)))) + with aiohttp.MultipartWriter() as writer: + for f in files: + writer.append(f) + writer.headers['Authorization'] = 'Bearer ' + token + resp = await session.request("post", build_url + '/upload', data=writer, headers=writer.headers) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + +async def upload_deltas(session, repo_path, build_url, token, deltas, refs, ignore_delta): + if not len(deltas): + return + + req = [] + for ref, commit in refs.items(): + # Skip screenshots here + parts = ref.split("/") + if len(parts) == 4 and (parts[0] == "app" or parts[0] =="runtime") and not should_skip_delta(parts[1], ignore_delta): + for delta in deltas: + # Only upload from-scratch deltas, as these are the only reused ones + if delta == commit: + print(" %s: %s" % (ref, delta)) + delta_name = delta_name_encode (delta) + delta_dir = repo_path + "/deltas/" + delta_name[:2] + "/" + delta_name[2:] + parts = os.listdir(delta_dir) + for part in parts: + req.append(AsyncNamedFilePart(delta_dir + "/" + part, filename = delta_name + "." + part + ".delta")) + + if len(req): + await upload_files(session, build_url, token, req) + + +async def upload_objects(session, repo_path, build_url, token, objects): + req = [] + total_size = 0 + for file_obj in objects: + named = get_object_multipart(repo_path, file_obj) + file_size = named.size + if total_size + file_size > UPLOAD_CHUNK_LIMIT: # The new object would bring us over the chunk limit + if len(req) > 0: # We already have some objects, upload those first + next_req = [named] + total_size = file_size + else: + next_req = [] + req.append(named) + total_size = 0 + await upload_files(session, build_url, token, req) + req = next_req + else: + total_size = total_size + file_size + req.append(named) + + # Upload any remainder + await upload_files(session, build_url, token, req) + +async def create_ref(session, build_url, token, ref, commit): + print("Creating ref %s with commit %s" % (ref, commit)) + resp = await session.post(build_url + "/build_ref", headers={'Authorization': 'Bearer ' + token}, json= { "ref": ref, "commit": commit} ) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + + data = await resp.json() + return data + +async def add_extra_ids(session, build_url, token, extra_ids): + print("Adding extra ids %s" % (extra_ids)) + resp = await session.post(build_url + "/add_extra_ids", headers={'Authorization': 'Bearer ' + token}, json= { "ids": extra_ids} ) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + + data = await resp.json() + return data + +async def get_build(session, build_url, token): + resp = await session.get(build_url, headers={'Authorization': 'Bearer ' + token}) + if resp.status != 200: + raise ApiError(resp, await resp.text()) + data = await resp.json() + return data + +# For stupid reasons this is a string with json, lets expand it +def reparse_job_results(job): + job["results"] = json.loads(job.get("results", "{}")) + return job + +async def get_job(session, job_url, token): + resp = await session.get(job_url, headers={'Authorization': 'Bearer ' + token}, json={}) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + data = await resp.json() + return data + +async def wait_for_job(session, job_url, token): + reported_delay = False + old_job_status = 0 + printed_len = 0 + iterations_since_change=0 + error_iterations = 0 + while True: + try: + resp = await session.get(job_url, headers={'Authorization': 'Bearer ' + token}, json={'log-offset': printed_len}) + async with resp: + if resp.status == 200: + error_iterations = 0 + job = await resp.json() + job_status = job['status'] + if job_status == 0 and not reported_delay: + reported_delay = True + start_after_struct = job.get("start_after", None) + if start_after_struct: + start_after = start_after_struct.get("secs_since_epoch", None) + now = time.time() + if start_after and start_after > now: + print("Waiting %d seconds before starting job" % (int(start_after - now))) + if job_status > 0 and old_job_status == 0: + print("/ Job was started"); + old_job_status = job_status + log = job['log'] + if len(log) > 0: + iterations_since_change=0 + for line in log.splitlines(True): + print("| %s" % line, end="") + printed_len = printed_len + len(log) + else: + iterations_since_change=iterations_since_change+1 + if job_status > 1: + if job_status == 2: + print("\ Job completed successfully") + else: + print("\ Job failed") + return job + else: + iterations_since_change=4 # Start at 4 so we ramp up the delay faster + error_iterations=error_iterations + 1 + if error_iterations <= 5: + print("Unexpected response %s getting job log, ignoring" % resp.status) + else: + raise ApiError(resp, await resp.text()) + except OSError as e: + if e.args[0] == errno.ECONNRESET: + # Client disconnected, retry + # Not sure exactly why, but i got a lot of ConnectionResetErrors here + # in tests. I guess the server stops reusing a http2 session after a bit + # Should be fine to retry with the backof + pass + else: + raise + # Some polling backoff to avoid loading the server + if iterations_since_change <= 1: + sleep_time=1 + elif iterations_since_change < 5: + sleep_time=3 + elif iterations_since_change < 15: + sleep_time=5 + elif iterations_since_change < 30: + sleep_time=10 + else: + sleep_time=60 + time.sleep(sleep_time) + +async def commit_build(session, build_url, eol, eol_rebase, wait, token): + print("Committing build %s" % (build_url)) + resp = await session.post(build_url + "/commit", headers={'Authorization': 'Bearer ' + token}, json= { + "endoflife": eol, "endoflife_rebase": eol_rebase + }) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + + job = await resp.json() + job_url = resp.headers['location']; + + if wait: + print("Waiting for commit job") + job = await wait_for_job(session, job_url, token); + + reparse_job_results(job) + job["location"] = job_url + return job + +async def publish_build(session, build_url, wait, token): + print("Publishing build %s" % (build_url)) + resp = await session.post(build_url + "/publish", headers={'Authorization': 'Bearer ' + token}, json= { } ) + async with resp: + if resp.status == 400: + body = await resp.text() + try: + msg = json.loads(body) + if msg.get("current-state", "") == "published": + print("the build has been already published") + return {} + except: + pass + + if resp.status != 200: + raise ApiError(resp, await resp.text()) + + job = await resp.json() + job_url = resp.headers['location']; + + if wait: + print("Waiting for publish job") + job = await wait_for_job(session, job_url, token); + + reparse_job_results(job) + job["location"] = job_url + return job + +async def purge_build(session, build_url, token): + print("Purging build %s" % (build_url)) + resp = await session.post(build_url + "/purge", headers={'Authorization': 'Bearer ' + token}, json= {} ) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + return await resp.json() + +async def create_token(session, manager_url, token, name, subject, scope, duration): + token_url = urljoin(manager_url, "/api/v1/token_subset") + resp = await session.post(token_url, headers={'Authorization': 'Bearer ' + token}, json = { + "name": name, + "sub": subject, + "scope": scope, + "duration": duration, + }) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + return await resp.json() + +def get_object_multipart(repo_path, object): + return AsyncNamedFilePart(repo_path + "/objects/" + object[:2] + "/" + object[2:], filename=object) + +async def create_command(session, args): + build_url = urljoin(args.manager_url, "/api/v1/build") + resp = await session.post(build_url, headers={'Authorization': 'Bearer ' + args.token}, json={ + "repo": args.repo + }) + async with resp: + if resp.status != 200: + raise ApiError(resp, await resp.text()) + data = await resp.json() + data["location"] = resp.headers['location'] + if not args.print_output: + print(resp.headers['location']) + return data + +def delta_name_part_encode(commit): + return base64.b64encode(binascii.unhexlify(commit), b"+_")[:-1].decode("utf-8") + +def delta_name_encode (delta): + return "-".join(map(delta_name_part_encode, delta.split("-"))) + +def should_skip_delta(id, globs): + if globs: + for glob in globs: + if fnmatch.fnmatch(id, glob): + return True + return False + +def build_url_to_api(build_url): + parts = urlparse(build_url) + path = os.path.dirname(os.path.dirname(parts.path)) + return urlunparse((parts.scheme, parts.netloc, path, None, None, None)) + +async def push_command(session, args): + local_repo = OSTree.Repo.new(Gio.File.new_for_path(args.repo_path)) + try: + local_repo.open(None) + except GLib.Error as err: + raise UsageException("Can't open repo %s: %s" % (args.repo_path, err.message)) from err + + refs = {} + if len(args.branches) == 0: + _, all_refs = local_repo.list_refs(None, None) + for ref in all_refs: + if ref.startswith("app/") or ref.startswith("runtime/") or ref.startswith("screenshots/"): + refs[ref] = all_refs[ref] + else: + for branch in args.branches: + _, rev = local_repo.resolve_rev(branch, False) + refs[branch] = rev + + if (args.minimal_token): + id = os.path.basename(urlparse(args.build_url).path) + token = create_token(args.build_url, args.token, "minimal-upload", "build/%s" % (id), ["upload"], 60*60)["token"] + else: + token = args.token + + print("Uploading refs to %s: %s"% (args.build_url, list(refs))) + + metadata_objects = local_needed_metadata(local_repo, refs.values()) + + print("Refs contain %d metadata objects" % (len(metadata_objects))) + + missing_metadata_objects = await missing_objects(session, args.build_url, token, list(metadata_objects)) + + print("Remote missing %d of those" % (len(missing_metadata_objects))) + + file_objects = local_needed_files(local_repo, missing_metadata_objects) + print("Has %d file objects for those" % (len(file_objects))) + + missing_file_objects = await missing_objects(session, args.build_url, token, list(file_objects)) + print("Remote missing %d of those" % (len(missing_file_objects))) + + # First upload all missing file objects + print("Uploading file objects") + await upload_objects(session, args.repo_path, args.build_url, token, missing_file_objects) + + # Then all the metadata + print("Uploading metadata objects") + await upload_objects(session, args.repo_path, args.build_url, token, missing_metadata_objects) + + _, deltas = local_repo.list_static_delta_names() + print("Uploading deltas") + await upload_deltas(session, args.repo_path, args.build_url, token, deltas, refs, args.ignore_delta) + + # Then the refs + for ref, commit in refs.items(): + await create_ref(session, args.build_url, token, ref, commit) + + # Then any extra ids + if args.extra_id: + await add_extra_ids(session, args.build_url, token, args.extra_id) + + commit_job = None + publish_job = None + update_job = None + + # Note, this always uses the full token, as the minimal one only has upload permissions + if args.commit or args.publish: + commit_job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.publish or args.wait, args.token) + + if args.publish: + publish_job = await publish_build(session, args.build_url, args.wait or args.wait_update, args.token) + update_job_id = publish_job.get("results", {}).get("update-repo-job", None) + if update_job_id: + print("Queued repo update job %d" %(update_job_id)) + update_job_url = build_url_to_api(args.build_url) + "/job/" + str(update_job_id) + if args.wait_update: + print("Waiting for repo update job") + update_job = await wait_for_job (session, update_job_url, token); + else: + update_job = await get_job(session, update_job_url, token) + reparse_job_results(update_job) + update_job["location"] = update_job_url + + data = await get_build(session, args.build_url, args.token) + if commit_job: + data["commit_job"] = commit_job + if publish_job: + data["publish_job"] = publish_job + if update_job: + data["update_job"] = update_job + return data + +async def commit_command(session, args): + job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.wait, args.token) + return job + +async def publish_command(session, args): + job = await publish_build(session, args.build_url, args.wait or args.wait_update, args.token) + update_job_id = job.get("results", {}).get("update-repo-job", None) + if update_job_id: + print("Queued repo update job %d" %(update_job_id)) + update_job_url = build_url_to_api(args.build_url) + "/job/" + str(update_job_id) + if args.wait_update: + print("Waiting for repo update job") + update_job = await wait_for_job(session, update_job_url, args.token); + else: + update_job = await get_job(session, update_job_url, args.token) + reparse_job_results(update_job) + update_job["location"] = update_job_url + return job + +async def purge_command(session, args): + job = await purge_build(session, args.build_url, args.token) + return job + +async def create_token_command(session, args): + data = await create_token(session, args.manager_url, args.token, args.name, args.subject, args.scope, args.duration) + if not args.print_output: + print(data['token']) + return data + +async def follow_job_command(session, args): + job = await wait_for_job(session, args.job_url, args.token) + return job + +async def run_with_session(args): + timeout = aiohttp.ClientTimeout(total=90*60) + async with aiohttp.ClientSession(timeout=timeout) as session: + result = await args.func(session, args) + return result + +if __name__ == '__main__': + progname = os.path.basename(sys.argv[0]) + + parser = ArgumentParser(prog=progname) + parser.add_argument('-v', '--verbose', action='store_true', + help='enable verbose output') + parser.add_argument('--debug', action='store_true', + help='enable debugging output') + parser.add_argument('--output', help='Write output json to file') + parser.add_argument('--print-output', action='store_true', help='Print output json') + parser.add_argument('--token', help='use this token') + parser.add_argument('--token-file', help='use token from file') + subparsers = parser.add_subparsers(title='subcommands', + dest='subparser_name', + description='valid subcommands', + help='additional help') + + create_parser = subparsers.add_parser('create', help='Create new build') + create_parser.add_argument('manager_url', help='remote repo manager url') + create_parser.add_argument('repo', help='repo name') + create_parser.set_defaults(func=create_command) + + push_parser = subparsers.add_parser('push', help='Push to repo manager') + push_parser.add_argument('build_url', help='remote build url') + push_parser.add_argument('repo_path', help='local repository') + push_parser.add_argument('branches', nargs='*', help='branches to push') + push_parser.add_argument('--commit', action='store_true', + help='commit build after pushing') + push_parser.add_argument('--publish', action='store_true', + help='publish build after committing') + push_parser.add_argument('--extra-id', action='append', help='add extra collection-id') + push_parser.add_argument('--ignore-delta', action='append', help='don\'t upload deltas matching this glob') + push_parser.add_argument('--wait', action='store_true', + help='wait for commit/publish to finish') + push_parser.add_argument('--wait-update', action='store_true', + help='wait for update-repo to finish') + push_parser.add_argument('--minimal-token', action='store_true', + help='Create minimal token for the upload') + push_parser.add_argument('--end-of-life', help='Set end of life') + push_parser.add_argument('--end-of-life-rebase', help='Set new ID which will supercede the current one') + push_parser.set_defaults(func=push_command) + + commit_parser = subparsers.add_parser('commit', help='Commit build') + commit_parser.add_argument('--wait', action='store_true', + help='wait for commit to finish') + commit_parser.add_argument('--end-of-life', help='Set end of life') + commit_parser.add_argument('--end-of-life-rebase', help='Set new ID which will supercede the current one') + commit_parser.add_argument('build_url', help='remote build url') + commit_parser.set_defaults(func=commit_command) + + publish_parser = subparsers.add_parser('publish', help='Publish build') + publish_parser.add_argument('--wait', action='store_true', + help='wait for publish to finish') + publish_parser.add_argument('--wait-update', action='store_true', + help='wait for update-repo to finish') + publish_parser.add_argument('build_url', help='remote build url') + publish_parser.set_defaults(func=publish_command) + + purge_parser = subparsers.add_parser('purge', help='Purge build') + purge_parser.add_argument('build_url', help='remote build url') + purge_parser.set_defaults(func=purge_command) + + create_token_parser = subparsers.add_parser('create-token', help='Create subset token') + create_token_parser.add_argument('manager_url', help='remote repo manager url') + create_token_parser.add_argument('name', help='Name') + create_token_parser.add_argument('subject', help='Subject') + create_token_parser.add_argument('scope', nargs='*', help='Scope') + create_token_parser.add_argument('--duration', help='Duration until expires, in seconds', + default=60*60*24, # Default duration is one day + type=int) + create_token_parser.set_defaults(func=create_token_command) + + follow_job_parser = subparsers.add_parser('follow-job', help='Follow existing job log') + follow_job_parser.add_argument('job_url', help='url of job') + follow_job_parser.set_defaults(func=follow_job_command) + + args = parser.parse_args() + + loglevel = logging.WARNING + if args.verbose: + loglevel = logging.INFO + if args.debug: + loglevel = logging.DEBUG + + logging.basicConfig(format='%(module)s: %(levelname)s: %(message)s', + level=loglevel, stream=sys.stderr) + + if not args.subparser_name: + print("No subcommand specified, see --help for usage") + exit(1) + + if not args.token: + if args.token_file: + file = open(args.token_file, 'rb') + args.token = file.read().splitlines()[0].decode("utf-8").strip() + elif "REPO_TOKEN" in os.environ: + args.token = os.environ["REPO_TOKEN"] + else: + print("No token available, pass with --token, --token-file or $REPO_TOKEN") + exit(1) + + + res = 1 + output = None + try: + loop = asyncio.get_event_loop() + result = loop.run_until_complete(run_with_session(args)) + + output = { + "command": args.subparser_name, + "result": result, + } + res = 0 + except SystemExit: + # Something called sys.exit(), lets just exit + res = 1 + raise # Pass on regular exit callse + except ApiError as e: + eprint(str(e)) + output = { + "command": args.subparser_name, + "error": e.repr(), + } + except UsageException as e: + eprint(str(e)) + output = { + "error": { + "type": "usage", + "details": { + "message": str(e), + } + } + } + except: + ei = sys.exc_info() + eprint("Unexpected %s exception in %s: %s" % (ei[0].__name__, args.subparser_name, ei[1])) + eprint(traceback.format_exc()) + output = { + "command": args.subparser_name, + "error": { + "type": "exception", + "details": { + "error-type": ei[0].__name__, + "message": str(ei[1]), + } + } + } + res = 1 + + if output: + if args.print_output: + print(json.dumps(output, indent=4)) + if args.output: + f = open(args.output,"w+") + f.write(json.dumps(output, indent=4)) + f.write("\n") + f.close() + exit(res) diff --git a/scripts/upload-to-flatpak-repo.sh b/scripts/upload-to-flatpak-repo.sh new file mode 100755 index 00000000..262a1635 --- /dev/null +++ b/scripts/upload-to-flatpak-repo.sh @@ -0,0 +1,23 @@ +#!/bin/bash + +if [ -z "$1" ]; then + echo "Missing repo to upload!" + exit 1 +fi + +if [ -n "${CI_COMMIT_TAG}" ]; then + BUILD_URL=$(./flat-manager-client create https://flatpak.neko.dev stable) +elif [ "master" = "${CI_COMMIT_REF_NAME}" ]; then + BUILD_URL=$(./flat-manager-client create https://flatpak.neko.dev nightly) +fi + +if [ -z "${BUILD_URL}" ]; then + echo "No upload to repo." + exit 0 +fi + +BUILD_URL=${BUILD_URL/http:/https:} + +./flat-manager-client push --commit $BUILD_URL $1 +./flat-manager-client publish $BUILD_URL + From 3e80f55f4eba2cbef8880b5d82d224e0aed63844 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 23 Feb 2021 21:18:02 +0100 Subject: [PATCH 31/45] Make flatpak ref autodownload --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 4bcd7254..f1056550 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,7 @@ nheko [![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.8.1) [![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://matrix-static.neko.dev/room/!TshDrgpBNBDmfDeEGN:neko.dev/) [![Nightly Flatpak](https://img.shields.io/badge/download-nheko--nightly-green)](https://raw.githubusercontent.com/Nheko-Reborn/nheko/master/nheko-nightly.flatpakref) +Download Nightly Flatpak [![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org) [![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko) Download on Flathub From 8ca819525a7fcc2cedc9fee596c67e3c263df9b2 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 23 Feb 2021 21:22:58 +0100 Subject: [PATCH 32/45] Remove unused link --- README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.md b/README.md index f1056550..fbdf5c1b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,7 @@ nheko [![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master) [![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.8.1) [![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://matrix-static.neko.dev/room/!TshDrgpBNBDmfDeEGN:neko.dev/) -[![Nightly Flatpak](https://img.shields.io/badge/download-nheko--nightly-green)](https://raw.githubusercontent.com/Nheko-Reborn/nheko/master/nheko-nightly.flatpakref) -Download Nightly Flatpak +Download Nightly Flatpak [![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org) [![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko) Download on Flathub From 150281b4de7f41b655743fee8bb1e68aa6373e13 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 23 Feb 2021 21:58:10 +0100 Subject: [PATCH 33/45] Add nightly flatpak repo --- nheko-nightly.flatpakrepo | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 nheko-nightly.flatpakrepo diff --git a/nheko-nightly.flatpakrepo b/nheko-nightly.flatpakrepo new file mode 100644 index 00000000..4fb1bc55 --- /dev/null +++ b/nheko-nightly.flatpakrepo @@ -0,0 +1,8 @@ +[Flatpak Repo] +Title=Nheko Nightly +Url=https://flatpak.neko.dev/repo/nightly +Homepage=https://nheko.im/ +Comment=Nheko nightly release repository +Description=Nheko nightly release repository +Icon=https://nheko.im/nheko-reborn/nheko/-/raw/master/resources/nheko.svg +GPGKey=mDMEXENMphYJKwYBBAHaRw8BAQdAqn+Eo42lPoGpJ5HaOf4nFGfxR0QtOggJTCfsdbOyL4e0Kk5pY29sYXMgV2VybmVyIDxuaWNvbGFzLndlcm5lckBob3RtYWlsLmRlPoiWBBMWCAA+FiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAlxDTVUCGwMFCQtJjooFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQkgauGyMeBbs2rQD/dAEoOGT21BL85A8LmPK743EboBAjoRbWcI1hHnvS28AA/3b3HYGwgvTC6hQLyz75zjpeO5ZaUtbezRyDUR4xabMAtCROaWNvbGFzIFdlcm5lciA8bmljb2xhc0BuZWtvZGV2Lm5ldD6IlgQTFggAPhYhBNWLRiQlpqNxJcb+25IGrhsjHgW7BQJcQ01GAhsDBQkLSY6KBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEJIGrhsjHgW7GxwBANT4gL03Uu9N5KmTBihC7B71+0r7M/azPbUh86NthCeIAQCF2JXa0axBKhgQF5fWC5ncL+m8ZpH7C5rzDqVgO82WALQnTmljb2xhcyBXZXJuZXIgPG5pY29sYXMud2VybmVyQGdteC5uZXQ+iJYEExYIAD4WIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbAwUJC0mOigULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCSBq4bIx4FuxU5APoCRDYlJW0oTsJs3lcTTB5Nsqb3X4iCEDCjIgsA3wtsIwEAlGBzD8ElCYi2+8m8esSRNlmpRcGoqgXbceLxPUXFpQu4OARcQ0ymEgorBgEEAZdVAQUBAQdAD8dBmT3iqrqdlxSw90L0SIH11fVxiX9MdWfBkTi6PzUDAQgHiH4EGBYIACYWIQTVi0YkJaajcSXG/tuSBq4bIx4FuwUCXENMpgIbDAUJC0mOigAKCRCSBq4bIx4Fu/LNAQDhH64IBic6h7H3uvtSAFT4xNn7Epobt2baIaDp7uKsQQEAyI+oc5dLknABwIOMrQQuZCmGejx9e4/8HEqLCdszhgG4MwRgNICHFgkrBgEEAdpHDwEBB0DR9eFFzfR62FIi7g+txcQenLvKFzhlyTz0wo3icOy6RYj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0gIcCGwIFCQlmAYAAgQkQkgauGyMeBbt2IAQZFggAHRYhBGz14re9h4cNPaFEKMjXXmEHc/LZBQJgNICHAAoJEMjXXmEHc/LZhVMBAPdYRspdeFh6E9BDxGubT705e/pZFdCHjCToDyxgdW5KAP9sU0hFI5VDHD1h98RzxSt7hc3jxyPSzbG1MBUJ9gbfCVhcAPsFfeZc3v5UBgmn4uICFEGjlzAWCQ7WctE6QTSkY5aL/wD9ETJH5lB+i/8km/sOBKQozXR0yHHw46gB6ZWMeN1wfgq4MwRgNPutFgkrBgEEAdpHDwEBB0APwMn0FJmnAds8IO8iCl/RHr7fz8xnpGd7E4zVgCNZpIj1BBgWCAAmFiEE1YtGJCWmo3Elxv7bkgauGyMeBbsFAmA0+60CGwIFCQANLwAAgQkQkgauGyMeBbt2IAQZFggAHRYhBAH7QBkzNfVIZJM93RNnXzGtBKQcBQJgNPutAAoJEBNnXzGtBKQcHnUA/0E2H5sxmfZ+EWFTso3X4NWu3uN2xF+MdNaY8C72f9H6AP91XaNmlB9gV61rg6wcB5E/j0998yWS9gltY1XY1ImqDPvlAP4sHFs5zuDazgKYxZ/kFhENCgEStdpnvJjt/DxmQPVT3AD/QK5vGoMTIeYjihv0QCnnRDfboTTZHlaEqJW8i02PQww= From 278710390394b036e0d96d6223396811106d2afa Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 23 Feb 2021 22:56:43 +0100 Subject: [PATCH 34/45] commit and publish flatpak in one command --- scripts/upload-to-flatpak-repo.sh | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/scripts/upload-to-flatpak-repo.sh b/scripts/upload-to-flatpak-repo.sh index 262a1635..f416132e 100755 --- a/scripts/upload-to-flatpak-repo.sh +++ b/scripts/upload-to-flatpak-repo.sh @@ -18,6 +18,5 @@ fi BUILD_URL=${BUILD_URL/http:/https:} -./flat-manager-client push --commit $BUILD_URL $1 -./flat-manager-client publish $BUILD_URL +./flat-manager-client push --commit --publish $BUILD_URL $1 From b9f72c2098957f43d9ff9d03a34891fc5a3121c9 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Tue, 23 Feb 2021 23:13:13 +0100 Subject: [PATCH 35/45] Update flat-manager-client --- scripts/flat-manager-client | 38 +++++++++++++++++++++---------------- 1 file changed, 22 insertions(+), 16 deletions(-) diff --git a/scripts/flat-manager-client b/scripts/flat-manager-client index 183ebf81..99f23b70 100755 --- a/scripts/flat-manager-client +++ b/scripts/flat-manager-client @@ -51,7 +51,7 @@ class UsageException(Exception): class ApiError(Exception): def __init__(self, response, body): - self.url = response.url + self.url = str(response.url) self.status = response.status try: @@ -69,7 +69,7 @@ class ApiError(Exception): def __str__(self): return "Api call to %s failed with status %d, details: %s" % (self.url, self.status, self.body) - return json.dumps(self.repr(), indent=4) + # This is similar to the regular payload, but opens the file lazily class AsyncNamedFilePart(aiohttp.payload.Payload): @@ -199,12 +199,6 @@ async def missing_objects(session, build_url, token, wanted): missing.extend(data["missing"]) return missing -@retry( - stop=stop_after_attempt(6), - wait=wait_fixed(20), - retry=retry_if_exception_type(ApiError), - reraise=True, -) async def upload_files(session, build_url, token, files): if len(files) == 0: return @@ -372,11 +366,15 @@ async def wait_for_job(session, job_url, token): sleep_time=60 time.sleep(sleep_time) -async def commit_build(session, build_url, eol, eol_rebase, wait, token): +async def commit_build(session, build_url, eol, eol_rebase, token_type, wait, token): print("Committing build %s" % (build_url)) - resp = await session.post(build_url + "/commit", headers={'Authorization': 'Bearer ' + token}, json= { - "endoflife": eol, "endoflife_rebase": eol_rebase - }) + json = { + "endoflife": eol, + "endoflife_rebase": eol_rebase + } + if token_type != None: + json['token_type'] = token_type + resp = await session.post(build_url + "/commit", headers={'Authorization': 'Bearer ' + token}, json=json) async with resp: if resp.status != 200: raise ApiError(resp, await resp.text()) @@ -429,7 +427,7 @@ async def purge_build(session, build_url, token): return await resp.json() async def create_token(session, manager_url, token, name, subject, scope, duration): - token_url = urljoin(manager_url, "/api/v1/token_subset") + token_url = urljoin(manager_url, "api/v1/token_subset") resp = await session.post(token_url, headers={'Authorization': 'Bearer ' + token}, json = { "name": name, "sub": subject, @@ -445,7 +443,7 @@ def get_object_multipart(repo_path, object): return AsyncNamedFilePart(repo_path + "/objects/" + object[:2] + "/" + object[2:], filename=object) async def create_command(session, args): - build_url = urljoin(args.manager_url, "/api/v1/build") + build_url = urljoin(args.manager_url, "api/v1/build") resp = await session.post(build_url, headers={'Authorization': 'Bearer ' + args.token}, json={ "repo": args.repo }) @@ -476,6 +474,12 @@ def build_url_to_api(build_url): path = os.path.dirname(os.path.dirname(parts.path)) return urlunparse((parts.scheme, parts.netloc, path, None, None, None)) +@retry( + stop=stop_after_attempt(6), + wait=wait_fixed(10), + retry=retry_if_exception_type(ApiError), + reraise=True, +) async def push_command(session, args): local_repo = OSTree.Repo.new(Gio.File.new_for_path(args.repo_path)) try: @@ -542,7 +546,7 @@ async def push_command(session, args): # Note, this always uses the full token, as the minimal one only has upload permissions if args.commit or args.publish: - commit_job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.publish or args.wait, args.token) + commit_job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.token_type, args.publish or args.wait, args.token) if args.publish: publish_job = await publish_build(session, args.build_url, args.wait or args.wait_update, args.token) @@ -568,7 +572,7 @@ async def push_command(session, args): return data async def commit_command(session, args): - job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.wait, args.token) + job = await commit_build(session, args.build_url, args.end_of_life, args.end_of_life_rebase, args.token_type, args.wait, args.token) return job async def publish_command(session, args): @@ -646,6 +650,7 @@ if __name__ == '__main__': help='Create minimal token for the upload') push_parser.add_argument('--end-of-life', help='Set end of life') push_parser.add_argument('--end-of-life-rebase', help='Set new ID which will supercede the current one') + push_parser.add_argument('--token-type', help='Set token type', type=int) push_parser.set_defaults(func=push_command) commit_parser = subparsers.add_parser('commit', help='Commit build') @@ -653,6 +658,7 @@ if __name__ == '__main__': help='wait for commit to finish') commit_parser.add_argument('--end-of-life', help='Set end of life') commit_parser.add_argument('--end-of-life-rebase', help='Set new ID which will supercede the current one') + commit_parser.add_argument('--token-type', help='Set token type', type=int) commit_parser.add_argument('build_url', help='remote build url') commit_parser.set_defaults(func=commit_command) From be85c51b91ad487a426d8f9fe4deb8c624e66014 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 24 Feb 2021 00:07:46 +0100 Subject: [PATCH 36/45] Use flatpakref from repo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index fbdf5c1b..cfcc6be6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ nheko [![Build status](https://ci.appveyor.com/api/projects/status/07qrqbfylsg4hw2h/branch/master?svg=true)](https://ci.appveyor.com/project/redsky17/nheko/branch/master) [![Stable Version](https://img.shields.io/badge/download-stable-green.svg)](https://github.com/Nheko-Reborn/nheko/releases/v0.8.1) [![Nightly](https://img.shields.io/badge/download-nightly-green.svg)](https://matrix-static.neko.dev/room/!TshDrgpBNBDmfDeEGN:neko.dev/) -Download Nightly Flatpak +Download Nightly Flatpak [![#nheko-reborn:matrix.org](https://img.shields.io/matrix/nheko-reborn:matrix.org.svg?label=%23nheko-reborn:matrix.org)](https://matrix.to/#/#nheko-reborn:matrix.org) [![AUR: nheko](https://img.shields.io/badge/AUR-nheko-blue.svg)](https://aur.archlinux.org/packages/nheko) Download on Flathub From 6800be24836a70f745c9736af631ebb296703359 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 24 Feb 2021 00:24:45 +0100 Subject: [PATCH 37/45] Copy upload steps from flathub --- scripts/upload-to-flatpak-repo.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/scripts/upload-to-flatpak-repo.sh b/scripts/upload-to-flatpak-repo.sh index f416132e..fdb37f82 100755 --- a/scripts/upload-to-flatpak-repo.sh +++ b/scripts/upload-to-flatpak-repo.sh @@ -18,5 +18,7 @@ fi BUILD_URL=${BUILD_URL/http:/https:} -./flat-manager-client push --commit --publish $BUILD_URL $1 +./flat-manager-client push $BUILD_URL $1 +./flat-manager-client commit --wait $BUILD_URL +./flat-manager-client publish --wait $BUILD_URL From db78cec62623fc9f55d515f465e6b1f6117906a2 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 24 Feb 2021 00:54:17 +0100 Subject: [PATCH 38/45] Add missing dependency to appimage --- AppImageBuilder.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/AppImageBuilder.yml b/AppImageBuilder.yml index 7b263058..fab81da9 100644 --- a/AppImageBuilder.yml +++ b/AppImageBuilder.yml @@ -79,6 +79,7 @@ AppDir: - libxv1 - libxxf86vm1 - libzstd1 + - qml-module-qt-labs-platform - qml-module-qtgraphicaleffects - qml-module-qtmultimedia - qml-module-qtquick-controls2 From 29a71741f4c0effd61f82b4e9897ba29529a8b6b Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 24 Feb 2021 01:37:26 +0100 Subject: [PATCH 39/45] Ensure we ask for confirmation when clicking on a matrix uri --- src/ChatPage.cpp | 20 +++++++++++++++++--- 1 file changed, 17 insertions(+), 3 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 9c814bd1..9ba2cc87 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -920,6 +920,13 @@ ChatPage::joinRoom(const QString &room) void ChatPage::joinRoomVia(const std::string &room_id, const std::vector &via) { + if (QMessageBox::Yes != + QMessageBox::question( + this, + tr("Confirm join"), + tr("Do you really want to join %1?").arg(QString::fromStdString(room_id)))) + return; + http::client()->join_room( room_id, via, [this, room_id](const mtx::responses::RoomId &, mtx::http::RequestErr err) { if (err) { @@ -1308,6 +1315,13 @@ ChatPage::startChat(QString userid) } } + if (QMessageBox::Yes != + QMessageBox::question( + this, + tr("Confirm invite"), + tr("Do you really want to start a private chat with %1?").arg(userid))) + return; + mtx::requests::CreateRoom req; req.preset = mtx::requests::Preset::PrivateChat; req.visibility = mtx::common::RoomVisibility::Private; @@ -1362,7 +1376,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) return; QString mxid2; - if (segments.size() == 4 && segments[2] == "e") { + if (segments.size() == 4 && segments[2] == "event") { if (segments[3].isEmpty()) return; else @@ -1400,7 +1414,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) } } - if (action == "join") { + if (action == "join" || action.isEmpty()) { joinRoomVia(targetRoomId, vias); } } else if (sigil1 == "r") { @@ -1418,7 +1432,7 @@ ChatPage::handleMatrixUri(const QByteArray &uri) } } - if (action == "join") { + if (action == "join" || action.isEmpty()) { joinRoomVia(mxid1.toStdString(), vias); } } From f6b5b24d64813f915fa05517ae9e5a19273669bf Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Wed, 24 Feb 2021 23:51:05 +0100 Subject: [PATCH 40/45] Allow editing via up and down arrows --- resources/qml/MessageInput.qml | 33 +++++++++++++++++++++++++++++++++ src/timeline/TimelineModel.cpp | 23 +++++++++++++++++++++-- src/timeline/TimelineModel.h | 1 + 3 files changed, 55 insertions(+), 2 deletions(-) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index c855ef90..9bb01471 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -209,6 +209,39 @@ Rectangle { } else if (event.key == Qt.Key_Down && popup.opened) { event.accepted = true; popup.down(); + } else if (event.key == Qt.Key_Up) { + if (cursorPosition == 0) { + event.accepted = true; + var idx = TimelineManager.timeline.edit ? TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) + 1 : 0; + while (true) { + var id = TimelineManager.timeline.indexToId(idx); + if (!id || TimelineManager.timeline.getDump(id, "").isEditable) { + TimelineManager.timeline.edit = id; + cursorPosition = 0; + break; + } + idx++; + } + } else if (cursorPosition == messageInput.length) { + event.accepted = true; + cursorPosition = 0; + } + } else if (event.key == Qt.Key_Down) { + if (cursorPosition == 0) { + event.accepted = true; + cursorPosition = messageInput.length; + } else if (cursorPosition == messageInput.length && TimelineManager.timeline.edit) { + event.accepted = true; + var idx = TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) - 1; + while (true) { + var id = TimelineManager.timeline.indexToId(idx); + if (!id || TimelineManager.timeline.getDump(id, "").isEditable) { + TimelineManager.timeline.edit = id; + break; + } + idx--; + } + } } } background: null diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index af4c6aa2..d46a313a 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -499,6 +499,8 @@ TimelineModel::data(const mtx::events::collections::TimelineEvents &event, int r data(event, static_cast(ProportionalHeight))); m.insert(names[Id], data(event, static_cast(Id))); m.insert(names[State], data(event, static_cast(State))); + m.insert(names[IsEdited], data(event, static_cast(IsEdited))); + m.insert(names[IsEditable], data(event, static_cast(IsEditable))); m.insert(names[IsEncrypted], data(event, static_cast(IsEncrypted))); m.insert(names[IsRoomEncrypted], data(event, static_cast(IsRoomEncrypted))); m.insert(names[ReplyTo], data(event, static_cast(ReplyTo))); @@ -1550,6 +1552,17 @@ TimelineModel::setEdit(QString newEdit) if (edit_.startsWith('m')) return; + if (newEdit.isEmpty()) { + resetEdit(); + return; + } + + if (edit_.isEmpty()) { + this->textBeforeEdit = input()->text(); + this->replyBeforeEdit = reply_; + nhlog::ui()->debug("Stored: {}", textBeforeEdit.toStdString()); + } + if (edit_ != newEdit) { auto ev = events.get(newEdit.toStdString(), ""); if (ev && mtx::accessors::sender(*ev) == http::client()->user_id().to_string()) { @@ -1584,8 +1597,14 @@ TimelineModel::resetEdit() if (!edit_.isEmpty()) { edit_ = ""; emit editChanged(edit_); - input()->setText(""); - resetReply(); + nhlog::ui()->debug("Restoring: {}", textBeforeEdit.toStdString()); + input()->setText(textBeforeEdit); + textBeforeEdit.clear(); + if (replyBeforeEdit.isEmpty()) + resetReply(); + else + setReply(replyBeforeEdit); + replyBeforeEdit.clear(); } } diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index 5f599741..c4df300f 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -337,6 +337,7 @@ private: QString currentId, currentReadId; QString reply_, edit_; + QString textBeforeEdit, replyBeforeEdit; std::vector typingUsers_; TimelineViewManager *manager_; From 345dc1e61fa51764fb4897903cf6be7579d66558 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 25 Feb 2021 00:50:17 +0100 Subject: [PATCH 41/45] Fix text input restoring after edits --- resources/qml/MessageInput.qml | 2 -- src/timeline/InputBar.cpp | 41 ++++++++++++++++++---------------- src/timeline/InputBar.h | 2 +- src/timeline/TimelineModel.h | 2 ++ 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 9bb01471..5a543ac9 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -177,7 +177,6 @@ Rectangle { } } TimelineManager.timeline.input.send(); - messageInput.clear(); event.accepted = true; } else if (event.key == Qt.Key_Tab) { event.accepted = true; @@ -334,7 +333,6 @@ Rectangle { ToolTip.text: qsTr("Send") onClicked: { TimelineManager.timeline.input.send(); - messageInput.clear(); } } diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp index 5ef38ac7..b1580f97 100644 --- a/src/timeline/InputBar.cpp +++ b/src/timeline/InputBar.cpp @@ -122,6 +122,20 @@ InputBar::insertMimeData(const QMimeData *md) } } +void +InputBar::setText(QString newText) +{ + if (history_.empty()) + history_.push_front(newText); + else + history_.front() = newText; + history_index_ = 0; + + if (history_.size() == INPUT_HISTORY_SIZE) + history_.pop_back(); + + emit textChanged(newText); +} void InputBar::updateState(int selectionStart_, int selectionEnd_, int cursorPosition_, QString text_) { @@ -202,6 +216,10 @@ InputBar::send() if (text().trimmed().isEmpty()) return; + nhlog::ui()->debug("Send: {}", text().toStdString()); + + auto wasEdit = !room->edit().isEmpty(); + if (text().startsWith('/')) { int command_end = text().indexOf(' '); if (command_end == -1) @@ -217,12 +235,10 @@ InputBar::send() message(text()); } - nhlog::ui()->debug("Send: {}", text().toStdString()); - - if (history_.size() == INPUT_HISTORY_SIZE) - history_.pop_back(); - history_.push_front(""); - history_index_ = 0; + if (!wasEdit) { + history_.push_front(""); + setText(""); + } } void @@ -278,12 +294,10 @@ InputBar::message(QString msg, MarkdownOverride useMarkdown) if (!room->reply().isEmpty()) { text.relations.relations.push_back( {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - room->resetReply(); } text.relations.relations.push_back( {mtx::common::RelationType::Replace, room->edit().toStdString()}); - room->resetEdit(); } else if (!room->reply().isEmpty()) { auto related = room->relatedInfo(room->reply()); @@ -313,7 +327,6 @@ InputBar::message(QString msg, MarkdownOverride useMarkdown) text.relations.relations.push_back( {mtx::common::RelationType::InReplyTo, related.related_event}); - room->resetReply(); } room->sendMessageEvent(text, mtx::events::EventType::RoomMessage); @@ -336,12 +349,10 @@ InputBar::emote(QString msg) if (!room->reply().isEmpty()) { emote.relations.relations.push_back( {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - room->resetReply(); } if (!room->edit().isEmpty()) { emote.relations.relations.push_back( {mtx::common::RelationType::Replace, room->edit().toStdString()}); - room->resetEdit(); } room->sendMessageEvent(emote, mtx::events::EventType::RoomMessage); @@ -372,12 +383,10 @@ InputBar::image(const QString &filename, if (!room->reply().isEmpty()) { image.relations.relations.push_back( {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - room->resetReply(); } if (!room->edit().isEmpty()) { image.relations.relations.push_back( {mtx::common::RelationType::Replace, room->edit().toStdString()}); - room->resetEdit(); } room->sendMessageEvent(image, mtx::events::EventType::RoomMessage); @@ -403,12 +412,10 @@ InputBar::file(const QString &filename, if (!room->reply().isEmpty()) { file.relations.relations.push_back( {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - room->resetReply(); } if (!room->edit().isEmpty()) { file.relations.relations.push_back( {mtx::common::RelationType::Replace, room->edit().toStdString()}); - room->resetEdit(); } room->sendMessageEvent(file, mtx::events::EventType::RoomMessage); @@ -435,12 +442,10 @@ InputBar::audio(const QString &filename, if (!room->reply().isEmpty()) { audio.relations.relations.push_back( {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - room->resetReply(); } if (!room->edit().isEmpty()) { audio.relations.relations.push_back( {mtx::common::RelationType::Replace, room->edit().toStdString()}); - room->resetEdit(); } room->sendMessageEvent(audio, mtx::events::EventType::RoomMessage); @@ -466,12 +471,10 @@ InputBar::video(const QString &filename, if (!room->reply().isEmpty()) { video.relations.relations.push_back( {mtx::common::RelationType::InReplyTo, room->reply().toStdString()}); - room->resetReply(); } if (!room->edit().isEmpty()) { video.relations.relations.push_back( {mtx::common::RelationType::Replace, room->edit().toStdString()}); - room->resetEdit(); } room->sendMessageEvent(video, mtx::events::EventType::RoomMessage); diff --git a/src/timeline/InputBar.h b/src/timeline/InputBar.h index 696a0dd9..4cb6da7b 100644 --- a/src/timeline/InputBar.h +++ b/src/timeline/InputBar.h @@ -41,7 +41,7 @@ public slots: QString text() const; QString previousText(); QString nextText(); - void setText(QString newText) { emit textChanged(newText); } + void setText(QString newText); void send(); void paste(bool fromMouse); diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index c4df300f..e02539bb 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -355,4 +355,6 @@ TimelineModel::sendMessageEvent(const T &content, mtx::events::EventType eventTy msgCopy.content = content; msgCopy.type = eventType; emit newMessageToSend(msgCopy); + resetReply(); + resetEdit(); } From 043737c8cb0f6ef058bc8578a566a6c5c218e231 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Thu, 25 Feb 2021 10:29:30 +0530 Subject: [PATCH 42/45] navigate to newly created rooms --- src/ChatPage.cpp | 13 +++++++++++-- src/ChatPage.h | 2 ++ 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp index 9ba2cc87..aae9271d 100644 --- a/src/ChatPage.cpp +++ b/src/ChatPage.cpp @@ -253,6 +253,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent) this, &ChatPage::updateGroupsInfo, communitiesList_, &CommunitiesList::setCommunities); connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom); + connect(this, &ChatPage::newRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection); connect(this, &ChatPage::notificationsRetrieved, this, &ChatPage::sendNotifications); connect(this, &ChatPage::highlightedNotifsRetrieved, @@ -967,8 +968,9 @@ ChatPage::createRoom(const mtx::requests::CreateRoom &req) return; } - emit showNotification( - tr("Room %1 created.").arg(QString::fromStdString(res.room_id.to_string()))); + QString newRoomId = QString::fromStdString(res.room_id.to_string()); + emit showNotification(tr("Room %1 created.").arg(newRoomId)); + emit newRoom(newRoomId); }); } @@ -989,6 +991,13 @@ ChatPage::leaveRoom(const QString &room_id) }); } +void +ChatPage::changeRoom(const QString &room_id) +{ + view_manager_->setHistoryView(room_id); + room_list_->highlightSelectedRoom(room_id); +} + void ChatPage::inviteUser(QString userid, QString reason) { diff --git a/src/ChatPage.h b/src/ChatPage.h index 917bd785..dc6b8299 100644 --- a/src/ChatPage.h +++ b/src/ChatPage.h @@ -154,6 +154,7 @@ signals: void tryInitialSyncCb(); void newSyncResponse(const mtx::responses::Sync &res); void leftRoom(const QString &room_id); + void newRoom(const QString &room_id); void initializeRoomList(QMap); void initializeViews(const mtx::responses::Rooms &rooms); @@ -201,6 +202,7 @@ signals: private slots: void logout(); void removeRoom(const QString &room_id); + void changeRoom(const QString &room_id); void dropToLoginPage(const QString &msg); void handleSyncResponse(const mtx::responses::Sync &res); From 4a5b9d014a7659223b3a820723b13f834b922c4f Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Thu, 25 Feb 2021 18:10:06 +0530 Subject: [PATCH 43/45] change mtxclient url, fix login page assert failure and dendrite registration bug --- CMakeLists.txt | 2 +- io.github.NhekoReborn.Nheko.json | 2 +- src/LoginPage.cpp | 13 +++++++++---- src/LoginPage.h | 1 + src/RegisterPage.cpp | 1 + 5 files changed, 13 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 05e54380..ff0d6a1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -358,7 +358,7 @@ if(USE_BUNDLED_MTXCLIENT) FetchContent_Declare( MatrixClient GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git - GIT_TAG fee5298f068394958c2de935836a2c145f273906 + GIT_TAG 004d4203ceb441239aafb17e1340cd063139d029 ) set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") diff --git a/io.github.NhekoReborn.Nheko.json b/io.github.NhekoReborn.Nheko.json index 453d6c8a..c014aaea 100644 --- a/io.github.NhekoReborn.Nheko.json +++ b/io.github.NhekoReborn.Nheko.json @@ -220,7 +220,7 @@ "name": "mtxclient", "sources": [ { - "commit": "fee5298f068394958c2de935836a2c145f273906", + "commit": "004d4203ceb441239aafb17e1340cd063139d029", "type": "git", "url": "https://github.com/Nheko-Reborn/mtxclient.git" } diff --git a/src/LoginPage.cpp b/src/LoginPage.cpp index 26a170c5..cd54431d 100644 --- a/src/LoginPage.cpp +++ b/src/LoginPage.cpp @@ -192,6 +192,11 @@ LoginPage::LoginPage(QWidget *parent) connect(sso_login_button_, &RaisedButton::clicked, this, [this]() { onLoginButtonClicked(LoginMethod::SSO); }); + connect(this, + &LoginPage::showErrorMessage, + this, + static_cast(&LoginPage::showError), + Qt::QueuedConnection); connect(matrixid_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); connect(password_input_, SIGNAL(returnPressed()), login_button_, SLOT(click())); connect(deviceName_, SIGNAL(returnPressed()), login_button_, SLOT(click())); @@ -422,8 +427,8 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod) : deviceName_->text().toStdString(), [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { if (err) { - showError(error_label_, - QString::fromStdString(err->matrix_error.error)); + showErrorMessage(error_label_, + QString::fromStdString(err->matrix_error.error)); emit errorOccurred(); return; } @@ -448,7 +453,7 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod) http::client()->login( req, [this](const mtx::responses::Login &res, mtx::http::RequestErr err) { if (err) { - showError( + showErrorMessage( error_label_, QString::fromStdString(err->matrix_error.error)); emit errorOccurred(); @@ -467,7 +472,7 @@ LoginPage::onLoginButtonClicked(LoginMethod loginMethod) sso->deleteLater(); }); connect(sso, &SSOHandler::ssoFailed, this, [this, sso]() { - showError(error_label_, tr("SSO login failed")); + showErrorMessage(error_label_, tr("SSO login failed")); emit errorOccurred(); sso->deleteLater(); }); diff --git a/src/LoginPage.h b/src/LoginPage.h index 2341c0ce..f6428cbb 100644 --- a/src/LoginPage.h +++ b/src/LoginPage.h @@ -59,6 +59,7 @@ signals: void versionOkCb(bool passwordSupported, bool ssoSupported); void loginOk(const mtx::responses::Login &res); + void showErrorMessage(QLabel *label, const QString &msg); protected: void paintEvent(QPaintEvent *event) override; diff --git a/src/RegisterPage.cpp b/src/RegisterPage.cpp index 44ad7a3d..004d5b98 100644 --- a/src/RegisterPage.cpp +++ b/src/RegisterPage.cpp @@ -277,6 +277,7 @@ RegisterPage::RegisterPage(QWidget *parent) if (!err) { http::client()->set_user(res.user_id); http::client()->set_access_token(res.access_token); + http::client()->set_device_id(res.device_id); emit registerOk(); return; From 8846a2a01303c87f04a823fcb4426a3fdb0b6ddf Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 25 Feb 2021 14:54:50 +0100 Subject: [PATCH 44/45] Fix potential issue with modifiers and edit shortcuts --- resources/qml/MessageInput.qml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/resources/qml/MessageInput.qml b/resources/qml/MessageInput.qml index 5a543ac9..48616c1a 100644 --- a/resources/qml/MessageInput.qml +++ b/resources/qml/MessageInput.qml @@ -208,7 +208,7 @@ Rectangle { } else if (event.key == Qt.Key_Down && popup.opened) { event.accepted = true; popup.down(); - } else if (event.key == Qt.Key_Up) { + } else if (event.key == Qt.Key_Up && event.modifiers == Qt.NoModifier) { if (cursorPosition == 0) { event.accepted = true; var idx = TimelineManager.timeline.edit ? TimelineManager.timeline.idToIndex(TimelineManager.timeline.edit) + 1 : 0; @@ -225,7 +225,7 @@ Rectangle { event.accepted = true; cursorPosition = 0; } - } else if (event.key == Qt.Key_Down) { + } else if (event.key == Qt.Key_Down && event.modifiers == Qt.NoModifier) { if (cursorPosition == 0) { event.accepted = true; cursorPosition = messageInput.length; From 1f373479b8e31a4febb954533f52615088859223 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Thu, 25 Feb 2021 15:15:59 +0100 Subject: [PATCH 45/45] Fix unaligned reads --- src/Cache.cpp | 46 ++++++++++++++++++++++++++++++---------------- 1 file changed, 30 insertions(+), 16 deletions(-) diff --git a/src/Cache.cpp b/src/Cache.cpp index 8cf66d21..c81840c6 100644 --- a/src/Cache.cpp +++ b/src/Cache.cpp @@ -102,6 +102,20 @@ namespace { std::unique_ptr instance_ = nullptr; } +template +static T +to(lmdb::val &value) +{ + static_assert(std::is_trivial_v, "Can only convert to trivial types!"); + T temp; + + if (value.size() < sizeof(T)) + throw lmdb::runtime_error(__func__, MDB_BAD_VALSIZE); + + std::memcpy(&temp, value.data(), sizeof(T)); + return temp; +} + bool Cache::isHiddenEvent(lmdb::txn &txn, mtx::events::collections::TimelineEvents e, @@ -1667,14 +1681,14 @@ Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id, uint64_t auto cursor = lmdb::cursor::open(txn, orderDb); if (index == std::numeric_limits::max()) { if (cursor.get(indexVal, event_id, forward ? MDB_FIRST : MDB_LAST)) { - index = *indexVal.data(); + index = to(indexVal); } else { messages.end_of_cache = true; return messages; } } else { if (cursor.get(indexVal, event_id, MDB_SET)) { - index = *indexVal.data(); + index = to(indexVal); } else { messages.end_of_cache = true; return messages; @@ -1708,7 +1722,7 @@ Cache::getTimelineMessages(lmdb::txn &txn, const std::string &room_id, uint64_t cursor.close(); // std::reverse(timeline.events.begin(), timeline.events.end()); - messages.next_index = *indexVal.data(); + messages.next_index = to(indexVal); messages.end_of_cache = !ret; return messages; @@ -1861,12 +1875,12 @@ Cache::getTimelineRange(const std::string &room_id) } TimelineRange range{}; - range.last = *indexVal.data(); + range.last = to(indexVal); if (!cursor.get(indexVal, val, MDB_FIRST)) { return {}; } - range.first = *indexVal.data(); + range.first = to(indexVal); return range; } @@ -1892,7 +1906,7 @@ Cache::getTimelineIndex(const std::string &room_id, std::string_view event_id) return {}; } - return *val.data(); + return to(val); } std::optional @@ -1920,7 +1934,7 @@ Cache::getEventIndex(const std::string &room_id, std::string_view event_id) return {}; } - return *val.data(); + return to(val); } std::optional> @@ -1951,7 +1965,7 @@ Cache::lastInvisibleEventAfter(const std::string &room_id, std::string_view even if (!success) { return {}; } - uint64_t prevIdx = *indexVal.data(); + uint64_t prevIdx = to(indexVal); std::string prevId{eventIdVal.data(), eventIdVal.size()}; auto cursor = lmdb::cursor::open(txn, eventOrderDb); @@ -1964,7 +1978,7 @@ Cache::lastInvisibleEventAfter(const std::string &room_id, std::string_view even if (lmdb::dbi_get(txn, timelineDb, lmdb::val(evId.data(), evId.size()), temp)) { return std::pair{prevIdx, std::string(prevId)}; } else { - prevIdx = *indexVal.data(); + prevIdx = to(indexVal); prevId = std::move(evId); } } @@ -1994,7 +2008,7 @@ Cache::getArrivalIndex(const std::string &room_id, std::string_view event_id) return {}; } - return *val.data(); + return to(val); } std::optional @@ -2775,13 +2789,13 @@ Cache::saveTimelineMessages(lmdb::txn &txn, uint64_t index = std::numeric_limits::max() / 2; auto cursor = lmdb::cursor::open(txn, orderDb); if (cursor.get(indexVal, val, MDB_LAST)) { - index = *indexVal.data(); + index = to(indexVal); } uint64_t msgIndex = std::numeric_limits::max() / 2; auto msgCursor = lmdb::cursor::open(txn, order2msgDb); if (msgCursor.get(indexVal, val, MDB_LAST)) { - msgIndex = *indexVal.data(); + msgIndex = to(indexVal); } bool first = true; @@ -2942,7 +2956,7 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message { auto cursor = lmdb::cursor::open(txn, orderDb); if (cursor.get(indexVal, val, MDB_FIRST)) { - index = *indexVal.data(); + index = to(indexVal); } } @@ -2950,7 +2964,7 @@ Cache::saveOldMessages(const std::string &room_id, const mtx::responses::Message { auto msgCursor = lmdb::cursor::open(txn, order2msgDb); if (msgCursor.get(indexVal, val, MDB_FIRST)) { - msgIndex = *indexVal.data(); + msgIndex = to(indexVal); } } @@ -3258,12 +3272,12 @@ Cache::deleteOldMessages() uint64_t first, last; if (cursor.get(indexVal, val, MDB_LAST)) { - last = *indexVal.data(); + last = to(indexVal); } else { continue; } if (cursor.get(indexVal, val, MDB_FIRST)) { - first = *indexVal.data(); + first = to(indexVal); } else { continue; }