diff options
-rw-r--r-- | CMakeLists.txt | 4 | ||||
-rw-r--r-- | resources/qml/TimelineView.qml | 22 | ||||
-rw-r--r-- | resources/qml/UserProfile.qml | 417 | ||||
-rw-r--r-- | src/MainWindow.cpp | 9 | ||||
-rw-r--r-- | src/MainWindow.h | 2 | ||||
-rw-r--r-- | src/dialogs/UserProfile.cpp | 305 | ||||
-rw-r--r-- | src/dialogs/UserProfile.h | 69 | ||||
-rw-r--r-- | src/timeline/TimelineModel.cpp | 4 | ||||
-rw-r--r-- | src/timeline/TimelineModel.h | 10 | ||||
-rw-r--r-- | src/timeline/TimelineViewManager.cpp | 20 | ||||
-rw-r--r-- | src/ui/UserProfile.cpp | 108 | ||||
-rw-r--r-- | src/ui/UserProfile.h | 120 | ||||
-rw-r--r-- | src/ui/UserProfileModel.cpp | 63 | ||||
-rw-r--r-- | src/ui/UserProfileModel.h | 30 |
14 files changed, 383 insertions, 800 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index c39ff3af..5ad8a625 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -240,7 +240,6 @@ set(SRC_FILES src/dialogs/ReCaptcha.cpp src/dialogs/ReadReceipts.cpp src/dialogs/RoomSettings.cpp - src/dialogs/UserProfile.cpp # Emoji src/emoji/Category.cpp @@ -280,7 +279,6 @@ set(SRC_FILES src/ui/Theme.cpp src/ui/ThemeManager.cpp src/ui/UserProfile.cpp - src/ui/UserProfileModel.cpp src/AvatarProvider.cpp src/BlurhashProvider.cpp @@ -449,7 +447,6 @@ qt5_wrap_cpp(MOC_HEADERS src/dialogs/ReCaptcha.h src/dialogs/ReadReceipts.h src/dialogs/RoomSettings.h - src/dialogs/UserProfile.h # Emoji src/emoji/Category.h @@ -486,7 +483,6 @@ qt5_wrap_cpp(MOC_HEADERS src/ui/Theme.h src/ui/ThemeManager.h src/ui/UserProfile.h - src/ui/UserProfileModel.h src/notifications/Manager.h diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 3618140a..ec634878 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -106,17 +106,23 @@ Page { } Connections { target: TimelineManager - onNewDeviceVerificationRequest: { + function onNewDeviceVerificationRequest(flow) { flow.userId = userId; flow.sender = false; flow.deviceId = deviceId; flow.tranId = transactionId; deviceVerificationList.add(flow.tranId); - var dialog = deviceVerificationDialog.createObject(timelineRoot, - {flow: flow}); + var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow}); dialog.show(); } } + Connections { + target: TimelineManager.timeline + function onOpenProfile(profile) { + var userProfile = userProfileComponent.createObject(timelineRoot,{profile: profile}); + userProfile.show(); + } + } Label { visible: !TimelineManager.timeline && !TimelineManager.isInitialSync @@ -293,10 +299,7 @@ Page { MouseArea { anchors.fill: parent - onClicked: { - userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)}); - userProfile.show(); - } + onClicked: chat.model.openUserProfile(modelData.userId) cursorShape: Qt.PointingHandCursor propagateComposedEvents: true } @@ -311,10 +314,7 @@ Page { MouseArea { anchors.fill: parent Layout.alignment: Qt.AlignHCenter - onClicked: { - userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)}); - userProfile.show(); - } + onClicked: chat.model.openUserProfile(modelData.userId) cursorShape: Qt.PointingHandCursor propagateComposedEvents: true } diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index db44ec15..df54367b 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -8,220 +8,211 @@ import im.nheko 1.0 import "./device-verification" ApplicationWindow{ - property var user_data - property var avatarUrl - property var colors: currentActivePalette - - id:userProfileDialog - height: 650 - width: 420 - modality:Qt.WindowModal - Layout.alignment: Qt.AlignHCenter - palette: colors - - Component { + property var profile + + id: userProfileDialog + height: 650 + width: 420 + modality: Qt.WindowModal + Layout.alignment: Qt.AlignHCenter + palette: colors + + Component { id: deviceVerificationDialog DeviceVerification {} } - Component{ - id: deviceVerificationFlow - DeviceVerificationFlow {} - } - - background: Item{ - id: userProfileItem - width: userProfileDialog.width - height: userProfileDialog.height - - // Layout.fillHeight : true - - ColumnLayout{ - anchors.fill: userProfileItem - width: userProfileDialog.width - spacing: 10 - - Avatar{ - id: userProfileAvatar - url: avatarUrl.replace("mxc://", "image://MxcImage/") - height: 130 - width: 130 - displayName: user_data.userName - userid: user_data.userId - Layout.alignment: Qt.AlignHCenter - Layout.margins : { - top: 10 - } - } - - Label{ - id: userProfileName - text: user_data.userName - fontSizeMode: Text.HorizontalFit - font.pixelSize: 20 - color:TimelineManager.userColor(user_data.userId, colors.window) - font.bold: true - Layout.alignment: Qt.AlignHCenter - } - - Label{ - id: matrixUserID - text: user_data.userId - fontSizeMode: Text.HorizontalFit - font.pixelSize: 15 - color:colors.text - Layout.alignment: Qt.AlignHCenter - } - - RowLayout{ - Layout.alignment: Qt.AlignHCenter - ImageButton{ - image:":/icons/icons/ui/do-not-disturb-rounded-sign.png" - Layout.margins: { - left: 5 - right: 5 - } - ToolTip.visible: hovered - ToolTip.text: qsTr("Ban the user") - onClicked : { - modelDeviceList.deviceList.banUser() - } - } - // ImageButton{ - // image:":/icons/icons/ui/volume-off-indicator.png" - // Layout.margins: { - // left: 5 - // right: 5 - // } - // ToolTip.visible: hovered - // ToolTip.text: qsTr("Ignore messages from this user") - // onClicked : { - // modelDeviceList.deviceList.ignoreUser() - // } - // } - ImageButton{ - image:":/icons/icons/ui/black-bubble-speech.png" - Layout.margins: { - left: 5 - right: 5 - } - ToolTip.visible: hovered - ToolTip.text: qsTr("Start a private chat") - onClicked : { - modelDeviceList.deviceList.startChat() - } - } - ImageButton{ - image:":/icons/icons/ui/round-remove-button.png" - Layout.margins: { - left: 5 - right: 5 - } - ToolTip.visible: hovered - ToolTip.text: qsTr("Kick the user") - onClicked : { - modelDeviceList.deviceList.kickUser() - } - } - } - - ScrollView { - implicitHeight: userProfileDialog.height/2 + 20 - implicitWidth: userProfileDialog.width-20 - clip: true - Layout.alignment: Qt.AlignHCenter - - ListView{ - id: devicelist - anchors.fill: parent - clip: true - spacing: 4 - - model: UserProfileModel{ - id: modelDeviceList - deviceList.userId : user_data.userId - } - - delegate: RowLayout{ - width: parent.width - Layout.margins : { - top : 50 - } - ColumnLayout{ - RowLayout{ - Text{ - Layout.fillWidth: true - color: colors.text - font.bold: true - Layout.alignment: Qt.AlignLeft - text: deviceID - } - Text{ - Layout.fillWidth: true - color:colors.text - Layout.alignment: Qt.AlignLeft - text: (verified_status == UserProfileList.VERIFIED?"V":(verified_status == UserProfileList.UNVERIFIED?"NV":"B")) - } - } - Text{ - Layout.fillWidth: true - color:colors.text - Layout.alignment: Qt.AlignRight - text: displayName - } - } - Button{ - id: verifyButton - text:"Verify" - onClicked: { - var newFlow = deviceVerificationFlow.createObject(userProfileDialog, - {userId : user_data.userId,sender: true,deviceId : model.deviceID}); - deviceVerificationList.add(newFlow.tranId); - var dialog = deviceVerificationDialog.createObject(userProfileDialog, - {flow: newFlow}); - dialog.show(); - } - Layout.margins:{ - right: 10 - } - palette { - button: "white" - } - contentItem: Text { - text: verifyButton.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - } - } - } - - Button{ - id: okbutton - text:"OK" - onClicked: userProfileDialog.close() - - Layout.alignment: Qt.AlignRight | Qt.AlignBottom - - Layout.margins : { - right : 10 - bottom: 5 - } - - palette { - button: "white" - } - - contentItem: Text { - text: okbutton.text - color: "black" - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - } - } - - Item { Layout.fillHeight: true } - } + Component{ + id: deviceVerificationFlow + DeviceVerificationFlow {} + } + + background: Item{ + id: userProfileItem + width: userProfileDialog.width + height: userProfileDialog.height + + // Layout.fillHeight : true + + ColumnLayout{ + anchors.fill: userProfileItem + width: userProfileDialog.width + spacing: 10 + + Avatar { + url: profile.avatarUrl.replace("mxc://", "image://MxcImage/") + height: 130 + width: 130 + displayName: profile.displayName + userid: profile.userid + Layout.alignment: Qt.AlignHCenter + Layout.margins : { + top: 10 + } + } + + Label { + text: profile.displayName + fontSizeMode: Text.HorizontalFit + font.pixelSize: 20 + color: TimelineManager.userColor(profile.userid, colors.window) + font.bold: true + Layout.alignment: Qt.AlignHCenter + } + + Label { + text: profile.userid + fontSizeMode: Text.HorizontalFit + font.pixelSize: 15 + color: colors.text + Layout.alignment: Qt.AlignHCenter + } + + RowLayout { + Layout.alignment: Qt.AlignHCenter + ImageButton { + image:":/icons/icons/ui/do-not-disturb-rounded-sign.png" + Layout.margins: { + left: 5 + right: 5 + } + ToolTip.visible: hovered + ToolTip.text: qsTr("Ban the user") + onClicked : { + profile.banUser() + } + } + // ImageButton{ + // image:":/icons/icons/ui/volume-off-indicator.png" + // Layout.margins: { + // left: 5 + // right: 5 + // } + // ToolTip.visible: hovered + // ToolTip.text: qsTr("Ignore messages from this user") + // onClicked : { + // profile.ignoreUser() + // } + // } + ImageButton{ + image:":/icons/icons/ui/black-bubble-speech.png" + Layout.margins: { + left: 5 + right: 5 + } + ToolTip.visible: hovered + ToolTip.text: qsTr("Start a private chat") + onClicked : { + profile.startChat() + } + } + ImageButton{ + image:":/icons/icons/ui/round-remove-button.png" + Layout.margins: { + left: 5 + right: 5 + } + ToolTip.visible: hovered + ToolTip.text: qsTr("Kick the user") + onClicked : { + profile.kickUser() + } + } + } + + ScrollView { + implicitHeight: userProfileDialog.height/2 + 20 + implicitWidth: userProfileDialog.width-20 + clip: true + Layout.alignment: Qt.AlignHCenter + + ListView{ + id: devicelist + anchors.fill: parent + clip: true + spacing: 4 + + model: profile.deviceList + + delegate: RowLayout{ + width: parent.width + Layout.margins : { + top : 50 + } + ColumnLayout{ + RowLayout{ + Text{ + Layout.fillWidth: true + color: colors.text + font.bold: true + Layout.alignment: Qt.AlignLeft + text: model.deviceId + } + Text{ + Layout.fillWidth: true + color:colors.text + Layout.alignment: Qt.AlignLeft + text: (model.verificationStatus == VerificationStatus.VERIFIED?"V":(model.verificationStatus == VerificationStatus.UNVERIFIED?"NV":"B")) + } + } + Text{ + Layout.fillWidth: true + color:colors.text + Layout.alignment: Qt.AlignRight + text: model.deviceName + } + } + Button{ + id: verifyButton + text:"Verify" + onClicked: { + var newFlow = deviceVerificationFlow.createObject(userProfileDialog, + {userId : profile.userid, sender: true, deviceId : model.deviceID}); + deviceVerificationList.add(newFlow.tranId); + var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow}); + dialog.show(); + } + Layout.margins:{ + right: 10 + } + palette { + button: "white" + } + contentItem: Text { + text: verifyButton.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + } + } + } + + Button{ + id: okbutton + text:"OK" + onClicked: userProfileDialog.close() + + Layout.alignment: Qt.AlignRight | Qt.AlignBottom + + Layout.margins : { + right : 10 + bottom: 5 + } + + palette { + button: "white" + } + + contentItem: Text { + text: okbutton.text + color: "black" + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + } + } + + Item { Layout.fillHeight: true } + } } diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp index cc1d868b..63b524c8 100644 --- a/src/MainWindow.cpp +++ b/src/MainWindow.cpp @@ -318,15 +318,6 @@ MainWindow::hasActiveUser() } void -MainWindow::openUserProfile(const QString &user_id, const QString &room_id) -{ - auto dialog = new dialogs::UserProfile(this); - dialog->init(user_id, room_id); - - showDialog(dialog); -} - -void MainWindow::openRoomSettings(const QString &room_id) { const auto roomToSearch = room_id.isEmpty() ? chat_page_->currentRoom() : ""; diff --git a/src/MainWindow.h b/src/MainWindow.h index e3e04698..2fc2d00f 100644 --- a/src/MainWindow.h +++ b/src/MainWindow.h @@ -25,7 +25,6 @@ #include <QSystemTrayIcon> #include "UserSettingsPage.h" -#include "dialogs/UserProfile.h" #include "ui/OverlayModal.h" #include "jdenticoninterface.h" @@ -76,7 +75,6 @@ public: void openLogoutDialog(); void openRoomSettings(const QString &room_id = ""); void openMemberListDialog(const QString &room_id = ""); - void openUserProfile(const QString &user_id, const QString &room_id); void openReadReceiptsDialog(const QString &event_id); void hideOverlay(); diff --git a/src/dialogs/UserProfile.cpp b/src/dialogs/UserProfile.cpp deleted file mode 100644 index 3415b127..00000000 --- a/src/dialogs/UserProfile.cpp +++ /dev/null @@ -1,305 +0,0 @@ -#include <QHBoxLayout> -#include <QLabel> -#include <QListWidget> -#include <QShortcut> -#include <QVBoxLayout> - -#include "Cache.h" -#include "ChatPage.h" -#include "Logging.h" -#include "MatrixClient.h" -#include "Utils.h" -#include "dialogs/UserProfile.h" -#include "ui/Avatar.h" -#include "ui/FlatButton.h" - -using namespace dialogs; - -Q_DECLARE_METATYPE(std::vector<DeviceInfo>) - -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 DEVICE_SPACING = 5; - -DeviceItem::DeviceItem(DeviceInfo device, QWidget *parent) - : QWidget(parent) - , info_(std::move(device)) -{ - QFont font; - font.setBold(true); - - auto deviceIdLabel = new QLabel(info_.device_id, this); - deviceIdLabel->setFont(font); - - auto layout = new QVBoxLayout{this}; - layout->addWidget(deviceIdLabel); - - if (!info_.display_name.isEmpty()) - layout->addWidget(new QLabel(info_.display_name, this)); - - layout->setMargin(0); - layout->setSpacing(4); -} - -UserProfile::UserProfile(QWidget *parent) - : QWidget(parent) -{ - setAutoFillBackground(true); - setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); - setAttribute(Qt::WA_DeleteOnClose, true); - - QIcon banIcon, kickIcon, ignoreIcon, startChatIcon; - - banIcon.addFile(":/icons/icons/ui/do-not-disturb-rounded-sign.png"); - banBtn_ = new FlatButton(this); - banBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE); - banBtn_->setCornerRadius(BUTTON_RADIUS); - banBtn_->setIcon(banIcon); - banBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS)); - banBtn_->setToolTip(tr("Ban the user from the room")); - - ignoreIcon.addFile(":/icons/icons/ui/volume-off-indicator.png"); - ignoreBtn_ = new FlatButton(this); - ignoreBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE); - ignoreBtn_->setCornerRadius(BUTTON_RADIUS); - ignoreBtn_->setIcon(ignoreIcon); - ignoreBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS)); - ignoreBtn_->setToolTip(tr("Ignore messages from this user")); - ignoreBtn_->setDisabled(true); // Not used yet. - - kickIcon.addFile(":/icons/icons/ui/round-remove-button.png"); - kickBtn_ = new FlatButton(this); - kickBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE); - kickBtn_->setCornerRadius(BUTTON_RADIUS); - kickBtn_->setIcon(kickIcon); - kickBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS)); - kickBtn_->setToolTip(tr("Kick the user from the room")); - - startChatIcon.addFile(":/icons/icons/ui/black-bubble-speech.png"); - startChat_ = new FlatButton(this); - startChat_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE); - startChat_->setCornerRadius(BUTTON_RADIUS); - startChat_->setIcon(startChatIcon); - startChat_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS)); - startChat_->setToolTip(tr("Start a conversation")); - - connect(startChat_, &QPushButton::clicked, this, [this]() { - auto user_id = userIdLabel_->text(); - - mtx::requests::CreateRoom req; - req.preset = mtx::requests::Preset::PrivateChat; - req.visibility = mtx::requests::Visibility::Private; - - if (utils::localUser() != user_id) - req.invite = {user_id.toStdString()}; - - emit ChatPage::instance()->createRoom(req); - }); - - connect(banBtn_, &QPushButton::clicked, this, [this] { - ChatPage::instance()->banUser(userIdLabel_->text(), ""); - }); - connect(kickBtn_, &QPushButton::clicked, this, [this] { - ChatPage::instance()->kickUser(userIdLabel_->text(), ""); - }); - - // Button line - auto btnLayout = new QHBoxLayout; - btnLayout->addStretch(1); - btnLayout->addWidget(startChat_); - btnLayout->addWidget(ignoreBtn_); - - btnLayout->addWidget(kickBtn_); - btnLayout->addWidget(banBtn_); - btnLayout->addStretch(1); - btnLayout->setSpacing(8); - btnLayout->setMargin(0); - - avatar_ = new Avatar(this, 128); - avatar_->setLetter("X"); - - QFont font; - font.setPointSizeF(font.pointSizeF() * 2); - - userIdLabel_ = new QLabel(this); - displayNameLabel_ = new QLabel(this); - displayNameLabel_->setFont(font); - - auto textLayout = new QVBoxLayout; - textLayout->addWidget(displayNameLabel_); - textLayout->addWidget(userIdLabel_); - textLayout->setAlignment(displayNameLabel_, Qt::AlignCenter | Qt::AlignTop); - textLayout->setAlignment(userIdLabel_, Qt::AlignCenter | Qt::AlignTop); - textLayout->setSpacing(TEXT_SPACING); - textLayout->setMargin(0); - - devices_ = new QListWidget{this}; - devices_->setFrameStyle(QFrame::NoFrame); - devices_->setSelectionMode(QAbstractItemView::NoSelection); - devices_->setAttribute(Qt::WA_MacShowFocusRect, 0); - devices_->setSpacing(DEVICE_SPACING); - - QFont descriptionLabelFont; - descriptionLabelFont.setWeight(65); - - devicesLabel_ = new QLabel(tr("Devices").toUpper(), this); - devicesLabel_->setFont(descriptionLabelFont); - devicesLabel_->hide(); - devicesLabel_->setFixedSize(devicesLabel_->sizeHint()); - - auto okBtn = new QPushButton("OK", this); - - auto closeLayout = new QHBoxLayout(); - closeLayout->setSpacing(15); - closeLayout->addStretch(1); - closeLayout->addWidget(okBtn); - - auto vlayout = new QVBoxLayout{this}; - vlayout->addWidget(avatar_, 0, Qt::AlignCenter | Qt::AlignTop); - vlayout->addLayout(textLayout); - vlayout->addLayout(btnLayout); - vlayout->addWidget(devicesLabel_, 0, Qt::AlignLeft); - vlayout->addWidget(devices_, 1); - vlayout->addLayout(closeLayout); - - QFont largeFont; - largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5); - - setMinimumWidth( - std::max(devices_->sizeHint().width() + 4 * WIDGET_MARGIN, conf::window::minModalWidth)); - setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Minimum); - - vlayout->setSpacing(WIDGET_SPACING); - vlayout->setContentsMargins(WIDGET_MARGIN, TOP_WIDGET_MARGIN, WIDGET_MARGIN, WIDGET_MARGIN); - - qRegisterMetaType<std::vector<DeviceInfo>>(); - - auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this); - connect(closeShortcut, &QShortcut::activated, this, &UserProfile::close); - connect(okBtn, &QPushButton::clicked, this, &UserProfile::close); -} - -void -UserProfile::resetToDefaults() -{ - avatar_->setLetter("X"); - devices_->clear(); - - ignoreBtn_->show(); - devices_->hide(); - devicesLabel_->hide(); -} - -void -UserProfile::init(const QString &userId, const QString &roomId) -{ - resetToDefaults(); - - auto displayName = cache::displayName(roomId, userId); - - userIdLabel_->setText(userId); - displayNameLabel_->setText(displayName); - avatar_->setLetter(utils::firstChar(displayName)); - - avatar_->setImage(roomId, userId); - - auto localUser = utils::localUser(); - - try { - bool hasMemberRights = - cache::hasEnoughPowerLevel({mtx::events::EventType::RoomMember}, - roomId.toStdString(), - localUser.toStdString()); - if (!hasMemberRights) { - kickBtn_->hide(); - banBtn_->hide(); - } else { - kickBtn_->show(); - banBtn_->show(); - } - } catch (const lmdb::error &e) { - nhlog::db()->warn("lmdb error: {}", e.what()); - } - - if (localUser == userId) { - // TODO: click on display name & avatar to change. - kickBtn_->hide(); - banBtn_->hide(); - ignoreBtn_->hide(); - } - - mtx::requests::QueryKeys req; - req.device_keys[userId.toStdString()] = {}; - - // A proxy object is used to emit the signal instead of the original object - // which might be destroyed by the time the http call finishes. - auto proxy = std::make_shared<Proxy>(); - QObject::connect(proxy.get(), &Proxy::done, this, &UserProfile::updateDeviceList); - - http::client()->query_keys( - req, - [user_id = userId.toStdString(), proxy = std::move(proxy)]( - const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { - if (err) { - nhlog::net()->warn("failed to query device keys: {} {}", - err->matrix_error.error, - static_cast<int>(err->status_code)); - // TODO: Notify the UI. - return; - } - - if (res.device_keys.empty() || - (res.device_keys.find(user_id) == res.device_keys.end())) { - nhlog::net()->warn("no devices retrieved {}", user_id); - return; - } - - auto devices = res.device_keys.at(user_id); - - std::vector<DeviceInfo> deviceInfo; - for (const auto &d : devices) { - auto device = d.second; - - // TODO: Verify signatures and ignore those that don't pass. - deviceInfo.emplace_back(DeviceInfo{ - QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name)}); - } - - std::sort(deviceInfo.begin(), - deviceInfo.end(), - [](const DeviceInfo &a, const DeviceInfo &b) { - return a.device_id > b.device_id; - }); - - if (!deviceInfo.empty()) - emit proxy->done(QString::fromStdString(user_id), deviceInfo); - }); -} - -void -UserProfile::updateDeviceList(const QString &user_id, const std::vector<DeviceInfo> &devices) -{ - if (user_id != userIdLabel_->text()) - return; - - for (const auto &dev : devices) { - auto deviceItem = new DeviceItem(dev, this); - auto item = new QListWidgetItem; - - item->setSizeHint(deviceItem->minimumSizeHint()); - item->setFlags(Qt::NoItemFlags); - item->setTextAlignment(Qt::AlignCenter); - - devices_->insertItem(devices_->count() - 1, item); - devices_->setItemWidget(item, deviceItem); - } - - devicesLabel_->show(); - devices_->show(); - adjustSize(); -} diff --git a/src/dialogs/UserProfile.h b/src/dialogs/UserProfile.h deleted file mode 100644 index 81276d2a..00000000 --- a/src/dialogs/UserProfile.h +++ /dev/null @@ -1,69 +0,0 @@ -#pragma once - -#include <QString> -#include <QWidget> - -class Avatar; -class FlatButton; -class QLabel; -class QListWidget; -class Toggle; - -struct DeviceInfo -{ - QString device_id; - QString display_name; -}; - -class Proxy : public QObject -{ - Q_OBJECT - -signals: - void done(const QString &user_id, const std::vector<DeviceInfo> &devices); -}; - -namespace dialogs { - -class DeviceItem : public QWidget -{ - Q_OBJECT - -public: - explicit DeviceItem(DeviceInfo device, QWidget *parent); - -private: - DeviceInfo info_; - - // Toggle *verifyToggle_; -}; - -class UserProfile : public QWidget -{ - Q_OBJECT -public: - explicit UserProfile(QWidget *parent = nullptr); - - void init(const QString &userId, const QString &roomId); - -private slots: - void updateDeviceList(const QString &user_id, const std::vector<DeviceInfo> &devices); - -private: - void resetToDefaults(); - - Avatar *avatar_; - - QLabel *userIdLabel_; - QLabel *displayNameLabel_; - - FlatButton *banBtn_; - FlatButton *kickBtn_; - FlatButton *ignoreBtn_; - FlatButton *startChat_; - - QLabel *devicesLabel_; - QListWidget *devices_; -}; - -} // dialogs diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index f41e7712..773a5a23 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -654,9 +654,9 @@ TimelineModel::viewDecryptedRawMessage(QString id) const } void -TimelineModel::openUserProfile(QString userid) const +TimelineModel::openUserProfile(QString userid) { - MainWindow::instance()->openUserProfile(userid, room_id_); + emit openProfile(new UserProfile(room_id_, userid, this)); } void diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h index f8a84f17..104a475c 100644 --- a/src/timeline/TimelineModel.h +++ b/src/timeline/TimelineModel.h @@ -9,7 +9,12 @@ #include <mtxclient/http/errors.hpp> #include "CacheCryptoStructs.h" +<<<<<<< HEAD #include "EventStore.h" +======= +#include "ReactionsModel.h" +#include "ui/UserProfile.h" +>>>>>>> Refactor UserProfile namespace mtx::http { using RequestErr = const std::optional<mtx::http::ClientError> &; @@ -188,7 +193,7 @@ public: Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE void viewRawMessage(QString id) const; Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; - Q_INVOKABLE void openUserProfile(QString userid) const; + Q_INVOKABLE void openUserProfile(QString userid); Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void readReceiptsAction(QString id) const; Q_INVOKABLE void redactEvent(QString id); @@ -256,8 +261,7 @@ signals: void replyChanged(QString reply); void paginationInProgressChanged(const bool); - void newMessageToSend(mtx::events::collections::TimelineEvents event); - void addPendingMessageToStore(mtx::events::collections::TimelineEvents event); + void openProfile(UserProfile *profile); private: void sendEncryptedMessage(const std::string txn_id, nlohmann::json content); diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp index 0b732232..81c8d6d3 100644 --- a/src/timeline/TimelineViewManager.cpp +++ b/src/timeline/TimelineViewManager.cpp @@ -16,10 +16,9 @@ #include "dialogs/ImageOverlay.h" #include "emoji/EmojiModel.h" #include "emoji/Provider.h" -#include "src/ui/UserProfile.h" -#include "src/ui/UserProfileModel.h" Q_DECLARE_METATYPE(mtx::events::collections::TimelineEvents) +Q_DECLARE_METATYPE(std::vector<DeviceInfo>) namespace msgs = mtx::events::msg; @@ -109,15 +108,28 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin 0, "MtxEvent", "Can't instantiate enum!"); + qmlRegisterUncreatableMetaObject(verification::staticMetaObject, + "im.nheko", + 1, + 0, + "VerificationStatus", + "Can't instantiate enum!"); + qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); qmlRegisterType<DeviceVerificationFlow>("im.nheko", 1, 0, "DeviceVerificationFlow"); - qmlRegisterType<UserProfileModel>("im.nheko", 1, 0, "UserProfileModel"); - qmlRegisterType<UserProfile>("im.nheko", 1, 0, "UserProfileList"); + qmlRegisterUncreatableType<UserProfile>( + "im.nheko", + 1, + 0, + "UserProfileModel", + "UserProfile needs to be instantiated on the C++ side"); qmlRegisterSingletonInstance("im.nheko", 1, 0, "TimelineManager", this); qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", settings.data()); qRegisterMetaType<mtx::events::collections::TimelineEvents>(); + qRegisterMetaType<std::vector<DeviceInfo>>(); + qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel"); qmlRegisterType<emoji::EmojiProxyModel>("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel"); qmlRegisterUncreatableType<QAbstractItemModel>( diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 8c6fb8e4..fde0044b 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -5,47 +5,72 @@ #include "Utils.h" #include "mtx/responses/crypto.hpp" -#include <iostream> // only for debugging +UserProfile::UserProfile(QString roomid, QString userid, QObject *parent) + : QObject(parent) + , roomid_(roomid) + , userid_(userid) +{ + fetchDeviceList(this->userid_); +} -Q_DECLARE_METATYPE(UserProfile::Status) +QHash<int, QByteArray> +DeviceInfoModel::roleNames() const +{ + return { + {DeviceId, "deviceId"}, + {DeviceName, "deviceName"}, + {VerificationStatus, "verificationStatus"}, + }; +} -UserProfile::UserProfile(QObject *parent) - : QObject(parent) +QVariant +DeviceInfoModel::data(const QModelIndex &index, int role) const { - qRegisterMetaType<UserProfile::Status>(); - connect( - this, &UserProfile::updateDeviceList, this, [this]() { fetchDeviceList(this->userId); }); - connect( - this, - &UserProfile::appendDeviceList, - this, - [this](QString device_id, QString device_name, UserProfile::Status verification_status) { - this->deviceList.push_back( - DeviceInfo{device_id, device_name, verification_status}); - }); + if (!index.isValid() || index.row() >= (int)deviceList_.size() || index.row() < 0) + return {}; + + switch (role) { + case DeviceId: + return deviceList_[index.row()].device_id; + case DeviceName: + return deviceList_[index.row()].display_name; + case VerificationStatus: + return QVariant::fromValue(deviceList_[index.row()].verification_status); + default: + return {}; + } } -std::vector<DeviceInfo> -UserProfile::getDeviceList() +void +DeviceInfoModel::reset(const std::vector<DeviceInfo> &deviceList) { - return this->deviceList; + beginResetModel(); + this->deviceList_ = std::move(deviceList); + endResetModel(); +} + +DeviceInfoModel * +UserProfile::deviceList() +{ + return &this->deviceList_; } QString -UserProfile::getUserId() +UserProfile::userid() { - return this->userId; + return this->userid_; } -void -UserProfile::setUserId(const QString &user_id) +QString +UserProfile::displayName() { - if (this->userId != userId) - return; - else { - this->userId = user_id; - emit UserProfile::userIdChanged(); - } + return cache::displayName(roomid_, userid_); +} + +QString +UserProfile::avatarUrl() +{ + return cache::avatarUrl(roomid_, userid_); } void @@ -74,27 +99,27 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res, auto device = d.second; // TODO: Verify signatures and ignore those that don't pass. - UserProfile::Status verified = UserProfile::Status::UNVERIFIED; + verification::Status verified = verification::Status::UNVERIFIED; if (cross_verified.has_value()) { if (std::find(cross_verified->begin(), cross_verified->end(), d.first) != cross_verified->end()) - verified = UserProfile::Status::VERIFIED; + verified = verification::Status::VERIFIED; } else if (device_verified.has_value()) { if (std::find(device_verified->device_verified.begin(), device_verified->device_verified.end(), d.first) != device_verified->device_verified.end()) - verified = UserProfile::Status::VERIFIED; + verified = verification::Status::VERIFIED; } else if (device_verified.has_value()) { if (std::find(device_verified->device_blocked.begin(), device_verified->device_blocked.end(), d.first) != device_verified->device_blocked.end()) - verified = UserProfile::Status::BLOCKED; + verified = verification::Status::BLOCKED; } - emit UserProfile::appendDeviceList( - QString::fromStdString(d.first), - QString::fromStdString(device.unsigned_info.device_display_name), - verified); + deviceInfo.push_back( + {QString::fromStdString(d.first), + QString::fromStdString(device.unsigned_info.device_display_name), + verified}); } // std::sort( @@ -102,8 +127,7 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res, // return a.device_id > b.device_id; // }); - this->deviceList = std::move(deviceInfo); - emit UserProfile::deviceListUpdated(); + this->deviceList_.queueReset(std::move(deviceInfo)); } void @@ -130,7 +154,7 @@ UserProfile::fetchDeviceList(const QString &userID) void UserProfile::banUser() { - ChatPage::instance()->banUser(this->userId, ""); + ChatPage::instance()->banUser(this->userid_, ""); } // void ignoreUser(){ @@ -140,7 +164,7 @@ UserProfile::banUser() void UserProfile::kickUser() { - ChatPage::instance()->kickUser(this->userId, ""); + ChatPage::instance()->kickUser(this->userid_, ""); } void @@ -149,7 +173,7 @@ UserProfile::startChat() mtx::requests::CreateRoom req; req.preset = mtx::requests::Preset::PrivateChat; req.visibility = mtx::requests::Visibility::Private; - if (utils::localUser() != this->userId) - req.invite = {this->userId.toStdString()}; + if (utils::localUser() != this->userid_) + req.invite = {this->userid_.toStdString()}; emit ChatPage::instance()->createRoom(req); } diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 1725b961..38002fff 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -1,34 +1,92 @@ #pragma once +#include <QAbstractListModel> #include <QObject> #include <QString> #include <QVector> #include "MatrixClient.h" -class DeviceInfo; +namespace verification { +Q_NAMESPACE -class UserProfile : public QObject +enum Status +{ + VERIFIED, + UNVERIFIED, + BLOCKED +}; +Q_ENUM_NS(Status) +} + +class DeviceInfo +{ +public: + DeviceInfo(const QString deviceID, + const QString displayName, + verification::Status verification_status_) + : device_id(deviceID) + , display_name(displayName) + , verification_status(verification_status_) + {} + DeviceInfo() + : verification_status(verification::UNVERIFIED) + {} + + QString device_id; + QString display_name; + + verification::Status verification_status; +}; + +class DeviceInfoModel : public QAbstractListModel { Q_OBJECT - Q_PROPERTY(QString userId READ getUserId WRITE setUserId NOTIFY userIdChanged) - Q_PROPERTY(std::vector<DeviceInfo> deviceList READ getDeviceList NOTIFY deviceListUpdated) public: - // constructor - explicit UserProfile(QObject *parent = 0); - // getters - std::vector<DeviceInfo> getDeviceList(); - QString getUserId(); - // setters - void setUserId(const QString &userId); - - enum Status + enum Roles { - VERIFIED, - UNVERIFIED, - BLOCKED + DeviceId, + DeviceName, + VerificationStatus, }; - Q_ENUM(Status) + + explicit DeviceInfoModel(QObject *parent = nullptr) + { + (void)parent; + connect(this, &DeviceInfoModel::queueReset, this, &DeviceInfoModel::reset); + }; + QHash<int, QByteArray> roleNames() const override; + int rowCount(const QModelIndex &parent = QModelIndex()) const + { + (void)parent; + return (int)deviceList_.size(); + } + QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; + +signals: + void queueReset(const std::vector<DeviceInfo> &deviceList); +public slots: + void reset(const std::vector<DeviceInfo> &deviceList); + +private: + std::vector<DeviceInfo> deviceList_; +}; + +class UserProfile : public QObject +{ + Q_OBJECT + Q_PROPERTY(QString displayName READ displayName CONSTANT) + Q_PROPERTY(QString userid READ userid CONSTANT) + Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT) + Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT) +public: + UserProfile(QString roomid, QString userid, QObject *parent = 0); + + DeviceInfoModel *deviceList(); + + QString userid(); + QString displayName(); + QString avatarUrl(); void fetchDeviceList(const QString &userID); Q_INVOKABLE void banUser(); @@ -36,37 +94,13 @@ public: Q_INVOKABLE void kickUser(); Q_INVOKABLE void startChat(); -signals: - void userIdChanged(); - void deviceListUpdated(); - void updateDeviceList(); - void appendDeviceList(const QString device_id, - const QString device_naem, - const UserProfile::Status verification_status); - private: - std::vector<DeviceInfo> deviceList; - QString userId; + QString roomid_, userid_; std::optional<std::string> cross_verified; + DeviceInfoModel deviceList_; void callback_fn(const mtx::responses::QueryKeys &res, mtx::http::RequestErr err, std::string user_id, std::optional<std::vector<std::string>> cross_verified); }; - -class DeviceInfo -{ -public: - DeviceInfo(const QString deviceID, - const QString displayName, - UserProfile::Status verification_status_) - : device_id(deviceID) - , display_name(displayName) - , verification_status(verification_status_) - {} - - QString device_id; - QString display_name; - UserProfile::Status verification_status; -}; \ No newline at end of file diff --git a/src/ui/UserProfileModel.cpp b/src/ui/UserProfileModel.cpp deleted file mode 100644 index 3fa8fe2d..00000000 --- a/src/ui/UserProfileModel.cpp +++ /dev/null @@ -1,63 +0,0 @@ -#include "UserProfileModel.h" -#include <QModelIndex> - -UserProfileModel::UserProfileModel(QObject *parent) - : QAbstractListModel(parent) - , deviceList(nullptr) -{ - this->deviceList = new UserProfile(this); - - connect(this->deviceList, &UserProfile::userIdChanged, this, [this]() { - emit this->deviceList->updateDeviceList(); - }); - connect(this->deviceList, &UserProfile::deviceListUpdated, this, [this]() { - beginResetModel(); - this->beginInsertRows( - QModelIndex(), 0, this->deviceList->getDeviceList().size() - 1); - this->endInsertRows(); - endResetModel(); - }); -} - -int -UserProfileModel::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid() || !this->deviceList) - return 0; - return this->deviceList->getDeviceList().size(); -} - -QVariant -UserProfileModel::data(const QModelIndex &index, int role) const -{ - if (!index.isValid() && - static_cast<int>(this->deviceList->getDeviceList().size()) <= index.row()) - return QVariant(); - - const DeviceInfo device = this->deviceList->getDeviceList().at(index.row()); - switch (role) { - case DEVICEID: - return QVariant(device.device_id); - case DISPLAYNAME: - return QVariant(device.display_name); - case VERIFIED_STATUS: - return device.verification_status; - } - return QVariant(); -} - -QHash<int, QByteArray> -UserProfileModel::roleNames() const -{ - QHash<int, QByteArray> names; - names[DEVICEID] = "deviceID"; - names[DISPLAYNAME] = "displayName"; - names[VERIFIED_STATUS] = "verified_status"; - return names; -} - -UserProfile * -UserProfileModel::getList() const -{ - return (this->deviceList); -} diff --git a/src/ui/UserProfileModel.h b/src/ui/UserProfileModel.h deleted file mode 100644 index ba7a2525..00000000 --- a/src/ui/UserProfileModel.h +++ /dev/null @@ -1,30 +0,0 @@ -#pragma once - -#include "UserProfile.h" -#include <QAbstractListModel> - -class UserProfile; // forward declaration of the class UserProfile - -class UserProfileModel : public QAbstractListModel -{ - Q_OBJECT - Q_PROPERTY(UserProfile *deviceList READ getList) - -public: - explicit UserProfileModel(QObject *parent = nullptr); - - enum - { - DEVICEID, - DISPLAYNAME, - VERIFIED_STATUS - }; - UserProfile *getList() const; - - int rowCount(const QModelIndex &parent = QModelIndex()) const override; - QVariant data(const QModelIndex &index, int role) const override; - virtual QHash<int, QByteArray> roleNames() const override; - -private: - UserProfile *deviceList; -}; \ No newline at end of file |