Refactor UserProfile

This commit is contained in:
Nicolas Werner 2020-07-04 04:24:28 +02:00 committed by CH Chethan Reddy
parent ac1fbbb69f
commit 08028d5c57
14 changed files with 366 additions and 783 deletions

View file

@ -240,7 +240,6 @@ set(SRC_FILES
src/dialogs/ReCaptcha.cpp src/dialogs/ReCaptcha.cpp
src/dialogs/ReadReceipts.cpp src/dialogs/ReadReceipts.cpp
src/dialogs/RoomSettings.cpp src/dialogs/RoomSettings.cpp
src/dialogs/UserProfile.cpp
# Emoji # Emoji
src/emoji/Category.cpp src/emoji/Category.cpp
@ -280,7 +279,6 @@ set(SRC_FILES
src/ui/Theme.cpp src/ui/Theme.cpp
src/ui/ThemeManager.cpp src/ui/ThemeManager.cpp
src/ui/UserProfile.cpp src/ui/UserProfile.cpp
src/ui/UserProfileModel.cpp
src/AvatarProvider.cpp src/AvatarProvider.cpp
src/BlurhashProvider.cpp src/BlurhashProvider.cpp
@ -449,7 +447,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/dialogs/ReCaptcha.h src/dialogs/ReCaptcha.h
src/dialogs/ReadReceipts.h src/dialogs/ReadReceipts.h
src/dialogs/RoomSettings.h src/dialogs/RoomSettings.h
src/dialogs/UserProfile.h
# Emoji # Emoji
src/emoji/Category.h src/emoji/Category.h
@ -486,7 +483,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/ui/Theme.h src/ui/Theme.h
src/ui/ThemeManager.h src/ui/ThemeManager.h
src/ui/UserProfile.h src/ui/UserProfile.h
src/ui/UserProfileModel.h
src/notifications/Manager.h src/notifications/Manager.h

View file

@ -106,17 +106,23 @@ Page {
} }
Connections { Connections {
target: TimelineManager target: TimelineManager
onNewDeviceVerificationRequest: { function onNewDeviceVerificationRequest(flow) {
flow.userId = userId; flow.userId = userId;
flow.sender = false; flow.sender = false;
flow.deviceId = deviceId; flow.deviceId = deviceId;
flow.tranId = transactionId; flow.tranId = transactionId;
deviceVerificationList.add(flow.tranId); deviceVerificationList.add(flow.tranId);
var dialog = deviceVerificationDialog.createObject(timelineRoot, var dialog = deviceVerificationDialog.createObject(timelineRoot, {flow: flow});
{flow: flow});
dialog.show(); dialog.show();
} }
} }
Connections {
target: TimelineManager.timeline
function onOpenProfile(profile) {
var userProfile = userProfileComponent.createObject(timelineRoot,{profile: profile});
userProfile.show();
}
}
Label { Label {
visible: !TimelineManager.timeline && !TimelineManager.isInitialSync visible: !TimelineManager.timeline && !TimelineManager.isInitialSync
@ -293,10 +299,7 @@ Page {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
onClicked: { onClicked: chat.model.openUserProfile(modelData.userId)
userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)});
userProfile.show();
}
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true propagateComposedEvents: true
} }
@ -311,10 +314,7 @@ Page {
MouseArea { MouseArea {
anchors.fill: parent anchors.fill: parent
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
onClicked: { onClicked: chat.model.openUserProfile(modelData.userId)
userProfile = userProfileComponent.createObject(timelineRoot,{user_data: modelData,avatarUrl:chat.model.avatarUrl(modelData.userId)});
userProfile.show();
}
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
propagateComposedEvents: true propagateComposedEvents: true
} }

View file

@ -8,220 +8,211 @@ import im.nheko 1.0
import "./device-verification" import "./device-verification"
ApplicationWindow{ ApplicationWindow{
property var user_data property var profile
property var avatarUrl
property var colors: currentActivePalette
id:userProfileDialog id: userProfileDialog
height: 650 height: 650
width: 420 width: 420
modality:Qt.WindowModal modality: Qt.WindowModal
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
palette: colors palette: colors
Component { Component {
id: deviceVerificationDialog id: deviceVerificationDialog
DeviceVerification {} DeviceVerification {}
} }
Component{ Component{
id: deviceVerificationFlow id: deviceVerificationFlow
DeviceVerificationFlow {} DeviceVerificationFlow {}
} }
background: Item{ background: Item{
id: userProfileItem id: userProfileItem
width: userProfileDialog.width width: userProfileDialog.width
height: userProfileDialog.height height: userProfileDialog.height
// Layout.fillHeight : true // Layout.fillHeight : true
ColumnLayout{ ColumnLayout{
anchors.fill: userProfileItem anchors.fill: userProfileItem
width: userProfileDialog.width width: userProfileDialog.width
spacing: 10 spacing: 10
Avatar{ Avatar {
id: userProfileAvatar url: profile.avatarUrl.replace("mxc://", "image://MxcImage/")
url: avatarUrl.replace("mxc://", "image://MxcImage/") height: 130
height: 130 width: 130
width: 130 displayName: profile.displayName
displayName: user_data.userName userid: profile.userid
userid: user_data.userId Layout.alignment: Qt.AlignHCenter
Layout.alignment: Qt.AlignHCenter Layout.margins : {
Layout.margins : { top: 10
top: 10 }
} }
}
Label{ Label {
id: userProfileName text: profile.displayName
text: user_data.userName fontSizeMode: Text.HorizontalFit
fontSizeMode: Text.HorizontalFit font.pixelSize: 20
font.pixelSize: 20 color: TimelineManager.userColor(profile.userid, colors.window)
color:TimelineManager.userColor(user_data.userId, colors.window) font.bold: true
font.bold: true Layout.alignment: Qt.AlignHCenter
Layout.alignment: Qt.AlignHCenter }
}
Label{ Label {
id: matrixUserID text: profile.userid
text: user_data.userId fontSizeMode: Text.HorizontalFit
fontSizeMode: Text.HorizontalFit font.pixelSize: 15
font.pixelSize: 15 color: colors.text
color:colors.text Layout.alignment: Qt.AlignHCenter
Layout.alignment: Qt.AlignHCenter }
}
RowLayout{ RowLayout {
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
ImageButton{ ImageButton {
image:":/icons/icons/ui/do-not-disturb-rounded-sign.png" image:":/icons/icons/ui/do-not-disturb-rounded-sign.png"
Layout.margins: { Layout.margins: {
left: 5 left: 5
right: 5 right: 5
} }
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Ban the user") ToolTip.text: qsTr("Ban the user")
onClicked : { onClicked : {
modelDeviceList.deviceList.banUser() profile.banUser()
} }
} }
// ImageButton{ // ImageButton{
// image:":/icons/icons/ui/volume-off-indicator.png" // image:":/icons/icons/ui/volume-off-indicator.png"
// Layout.margins: { // Layout.margins: {
// left: 5 // left: 5
// right: 5 // right: 5
// } // }
// ToolTip.visible: hovered // ToolTip.visible: hovered
// ToolTip.text: qsTr("Ignore messages from this user") // ToolTip.text: qsTr("Ignore messages from this user")
// onClicked : { // onClicked : {
// modelDeviceList.deviceList.ignoreUser() // profile.ignoreUser()
// } // }
// } // }
ImageButton{ ImageButton{
image:":/icons/icons/ui/black-bubble-speech.png" image:":/icons/icons/ui/black-bubble-speech.png"
Layout.margins: { Layout.margins: {
left: 5 left: 5
right: 5 right: 5
} }
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Start a private chat") ToolTip.text: qsTr("Start a private chat")
onClicked : { onClicked : {
modelDeviceList.deviceList.startChat() profile.startChat()
} }
} }
ImageButton{ ImageButton{
image:":/icons/icons/ui/round-remove-button.png" image:":/icons/icons/ui/round-remove-button.png"
Layout.margins: { Layout.margins: {
left: 5 left: 5
right: 5 right: 5
} }
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Kick the user") ToolTip.text: qsTr("Kick the user")
onClicked : { onClicked : {
modelDeviceList.deviceList.kickUser() profile.kickUser()
} }
} }
} }
ScrollView { ScrollView {
implicitHeight: userProfileDialog.height/2 + 20 implicitHeight: userProfileDialog.height/2 + 20
implicitWidth: userProfileDialog.width-20 implicitWidth: userProfileDialog.width-20
clip: true clip: true
Layout.alignment: Qt.AlignHCenter Layout.alignment: Qt.AlignHCenter
ListView{ ListView{
id: devicelist id: devicelist
anchors.fill: parent anchors.fill: parent
clip: true clip: true
spacing: 4 spacing: 4
model: UserProfileModel{ model: profile.deviceList
id: modelDeviceList
deviceList.userId : user_data.userId
}
delegate: RowLayout{ delegate: RowLayout{
width: parent.width width: parent.width
Layout.margins : { Layout.margins : {
top : 50 top : 50
} }
ColumnLayout{ ColumnLayout{
RowLayout{ RowLayout{
Text{ Text{
Layout.fillWidth: true Layout.fillWidth: true
color: colors.text color: colors.text
font.bold: true font.bold: true
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: deviceID text: model.deviceId
} }
Text{ Text{
Layout.fillWidth: true Layout.fillWidth: true
color:colors.text color:colors.text
Layout.alignment: Qt.AlignLeft Layout.alignment: Qt.AlignLeft
text: (verified_status == UserProfileList.VERIFIED?"V":(verified_status == UserProfileList.UNVERIFIED?"NV":"B")) text: (model.verificationStatus == VerificationStatus.VERIFIED?"V":(model.verificationStatus == VerificationStatus.UNVERIFIED?"NV":"B"))
} }
} }
Text{ Text{
Layout.fillWidth: true Layout.fillWidth: true
color:colors.text color:colors.text
Layout.alignment: Qt.AlignRight Layout.alignment: Qt.AlignRight
text: displayName text: model.deviceName
} }
} }
Button{ Button{
id: verifyButton id: verifyButton
text:"Verify" text:"Verify"
onClicked: { onClicked: {
var newFlow = deviceVerificationFlow.createObject(userProfileDialog, var newFlow = deviceVerificationFlow.createObject(userProfileDialog,
{userId : user_data.userId,sender: true,deviceId : model.deviceID}); {userId : profile.userid, sender: true, deviceId : model.deviceID});
deviceVerificationList.add(newFlow.tranId); deviceVerificationList.add(newFlow.tranId);
var dialog = deviceVerificationDialog.createObject(userProfileDialog, var dialog = deviceVerificationDialog.createObject(userProfileDialog, {flow: newFlow});
{flow: newFlow}); dialog.show();
dialog.show(); }
} Layout.margins:{
Layout.margins:{ right: 10
right: 10 }
} palette {
palette { button: "white"
button: "white" }
} contentItem: Text {
contentItem: Text { text: verifyButton.text
text: verifyButton.text color: "black"
color: "black" horizontalAlignment: Text.AlignHCenter
horizontalAlignment: Text.AlignHCenter verticalAlignment: Text.AlignVCenter
verticalAlignment: Text.AlignVCenter }
} }
} }
} }
} }
}
Button{ Button{
id: okbutton id: okbutton
text:"OK" text:"OK"
onClicked: userProfileDialog.close() onClicked: userProfileDialog.close()
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
Layout.margins : { Layout.alignment: Qt.AlignRight | Qt.AlignBottom
right : 10
bottom: 5
}
palette { Layout.margins : {
button: "white" right : 10
} bottom: 5
}
contentItem: Text { palette {
text: okbutton.text button: "white"
color: "black" }
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
Item { Layout.fillHeight: true } contentItem: Text {
} text: okbutton.text
color: "black"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
Item { Layout.fillHeight: true }
}
} }

View file

@ -317,15 +317,6 @@ MainWindow::hasActiveUser()
settings.contains("auth/user_id"); settings.contains("auth/user_id");
} }
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 void
MainWindow::openRoomSettings(const QString &room_id) MainWindow::openRoomSettings(const QString &room_id)
{ {

View file

@ -25,7 +25,6 @@
#include <QSystemTrayIcon> #include <QSystemTrayIcon>
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
#include "dialogs/UserProfile.h"
#include "ui/OverlayModal.h" #include "ui/OverlayModal.h"
#include "jdenticoninterface.h" #include "jdenticoninterface.h"
@ -76,7 +75,6 @@ public:
void openLogoutDialog(); void openLogoutDialog();
void openRoomSettings(const QString &room_id = ""); void openRoomSettings(const QString &room_id = "");
void openMemberListDialog(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 openReadReceiptsDialog(const QString &event_id);
void hideOverlay(); void hideOverlay();

View file

@ -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();
}

View file

@ -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

View file

@ -654,9 +654,9 @@ TimelineModel::viewDecryptedRawMessage(QString id) const
} }
void 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 void

View file

@ -9,7 +9,12 @@
#include <mtxclient/http/errors.hpp> #include <mtxclient/http/errors.hpp>
#include "CacheCryptoStructs.h" #include "CacheCryptoStructs.h"
<<<<<<< HEAD
#include "EventStore.h" #include "EventStore.h"
=======
#include "ReactionsModel.h"
#include "ui/UserProfile.h"
>>>>>>> Refactor UserProfile
namespace mtx::http { namespace mtx::http {
using RequestErr = const std::optional<mtx::http::ClientError> &; using RequestErr = const std::optional<mtx::http::ClientError> &;
@ -188,7 +193,7 @@ public:
Q_INVOKABLE QString escapeEmoji(QString str) const; Q_INVOKABLE QString escapeEmoji(QString str) const;
Q_INVOKABLE void viewRawMessage(QString id) const; Q_INVOKABLE void viewRawMessage(QString id) const;
Q_INVOKABLE void viewDecryptedRawMessage(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 replyAction(QString id);
Q_INVOKABLE void readReceiptsAction(QString id) const; Q_INVOKABLE void readReceiptsAction(QString id) const;
Q_INVOKABLE void redactEvent(QString id); Q_INVOKABLE void redactEvent(QString id);
@ -256,8 +261,7 @@ signals:
void replyChanged(QString reply); void replyChanged(QString reply);
void paginationInProgressChanged(const bool); void paginationInProgressChanged(const bool);
void newMessageToSend(mtx::events::collections::TimelineEvents event); void openProfile(UserProfile *profile);
void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
private: private:
void sendEncryptedMessage(const std::string txn_id, nlohmann::json content); void sendEncryptedMessage(const std::string txn_id, nlohmann::json content);

View file

@ -16,10 +16,9 @@
#include "dialogs/ImageOverlay.h" #include "dialogs/ImageOverlay.h"
#include "emoji/EmojiModel.h" #include "emoji/EmojiModel.h"
#include "emoji/Provider.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(mtx::events::collections::TimelineEvents)
Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
namespace msgs = mtx::events::msg; namespace msgs = mtx::events::msg;
@ -109,15 +108,28 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
0, 0,
"MtxEvent", "MtxEvent",
"Can't instantiate enum!"); "Can't instantiate enum!");
qmlRegisterUncreatableMetaObject(verification::staticMetaObject,
"im.nheko",
1,
0,
"VerificationStatus",
"Can't instantiate enum!");
qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice"); qmlRegisterType<DelegateChoice>("im.nheko", 1, 0, "DelegateChoice");
qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser"); qmlRegisterType<DelegateChooser>("im.nheko", 1, 0, "DelegateChooser");
qmlRegisterType<DeviceVerificationFlow>("im.nheko", 1, 0, "DeviceVerificationFlow"); qmlRegisterType<DeviceVerificationFlow>("im.nheko", 1, 0, "DeviceVerificationFlow");
qmlRegisterType<UserProfileModel>("im.nheko", 1, 0, "UserProfileModel"); qmlRegisterUncreatableType<UserProfile>(
qmlRegisterType<UserProfile>("im.nheko", 1, 0, "UserProfileList"); "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, "TimelineManager", this);
qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", settings.data()); qmlRegisterSingletonInstance("im.nheko", 1, 0, "Settings", settings.data());
qRegisterMetaType<mtx::events::collections::TimelineEvents>(); qRegisterMetaType<mtx::events::collections::TimelineEvents>();
qRegisterMetaType<std::vector<DeviceInfo>>();
qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel"); qmlRegisterType<emoji::EmojiModel>("im.nheko.EmojiModel", 1, 0, "EmojiModel");
qmlRegisterType<emoji::EmojiProxyModel>("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel"); qmlRegisterType<emoji::EmojiProxyModel>("im.nheko.EmojiModel", 1, 0, "EmojiProxyModel");
qmlRegisterUncreatableType<QAbstractItemModel>( qmlRegisterUncreatableType<QAbstractItemModel>(

View file

@ -5,47 +5,72 @@
#include "Utils.h" #include "Utils.h"
#include "mtx/responses/crypto.hpp" #include "mtx/responses/crypto.hpp"
#include <iostream> // only for debugging UserProfile::UserProfile(QString roomid, QString userid, QObject *parent)
Q_DECLARE_METATYPE(UserProfile::Status)
UserProfile::UserProfile(QObject *parent)
: QObject(parent) : QObject(parent)
, roomid_(roomid)
, userid_(userid)
{ {
qRegisterMetaType<UserProfile::Status>(); fetchDeviceList(this->userid_);
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});
});
} }
std::vector<DeviceInfo> QHash<int, QByteArray>
UserProfile::getDeviceList() DeviceInfoModel::roleNames() const
{ {
return this->deviceList; return {
{DeviceId, "deviceId"},
{DeviceName, "deviceName"},
{VerificationStatus, "verificationStatus"},
};
} }
QString QVariant
UserProfile::getUserId() DeviceInfoModel::data(const QModelIndex &index, int role) const
{ {
return this->userId; 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 {};
}
} }
void void
UserProfile::setUserId(const QString &user_id) DeviceInfoModel::reset(const std::vector<DeviceInfo> &deviceList)
{ {
if (this->userId != userId) beginResetModel();
return; this->deviceList_ = std::move(deviceList);
else { endResetModel();
this->userId = user_id; }
emit UserProfile::userIdChanged();
} DeviceInfoModel *
UserProfile::deviceList()
{
return &this->deviceList_;
}
QString
UserProfile::userid()
{
return this->userid_;
}
QString
UserProfile::displayName()
{
return cache::displayName(roomid_, userid_);
}
QString
UserProfile::avatarUrl()
{
return cache::avatarUrl(roomid_, userid_);
} }
void void
@ -74,27 +99,27 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
auto device = d.second; auto device = d.second;
// TODO: Verify signatures and ignore those that don't pass. // 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 (cross_verified.has_value()) {
if (std::find(cross_verified->begin(), cross_verified->end(), d.first) != if (std::find(cross_verified->begin(), cross_verified->end(), d.first) !=
cross_verified->end()) cross_verified->end())
verified = UserProfile::Status::VERIFIED; verified = verification::Status::VERIFIED;
} else if (device_verified.has_value()) { } else if (device_verified.has_value()) {
if (std::find(device_verified->device_verified.begin(), if (std::find(device_verified->device_verified.begin(),
device_verified->device_verified.end(), device_verified->device_verified.end(),
d.first) != 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()) { } else if (device_verified.has_value()) {
if (std::find(device_verified->device_blocked.begin(), if (std::find(device_verified->device_blocked.begin(),
device_verified->device_blocked.end(), device_verified->device_blocked.end(),
d.first) != device_verified->device_blocked.end()) d.first) != device_verified->device_blocked.end())
verified = UserProfile::Status::BLOCKED; verified = verification::Status::BLOCKED;
} }
emit UserProfile::appendDeviceList( deviceInfo.push_back(
QString::fromStdString(d.first), {QString::fromStdString(d.first),
QString::fromStdString(device.unsigned_info.device_display_name), QString::fromStdString(device.unsigned_info.device_display_name),
verified); verified});
} }
// std::sort( // std::sort(
@ -102,8 +127,7 @@ UserProfile::callback_fn(const mtx::responses::QueryKeys &res,
// return a.device_id > b.device_id; // return a.device_id > b.device_id;
// }); // });
this->deviceList = std::move(deviceInfo); this->deviceList_.queueReset(std::move(deviceInfo));
emit UserProfile::deviceListUpdated();
} }
void void
@ -130,7 +154,7 @@ UserProfile::fetchDeviceList(const QString &userID)
void void
UserProfile::banUser() UserProfile::banUser()
{ {
ChatPage::instance()->banUser(this->userId, ""); ChatPage::instance()->banUser(this->userid_, "");
} }
// void ignoreUser(){ // void ignoreUser(){
@ -140,7 +164,7 @@ UserProfile::banUser()
void void
UserProfile::kickUser() UserProfile::kickUser()
{ {
ChatPage::instance()->kickUser(this->userId, ""); ChatPage::instance()->kickUser(this->userid_, "");
} }
void void
@ -149,7 +173,7 @@ UserProfile::startChat()
mtx::requests::CreateRoom req; mtx::requests::CreateRoom req;
req.preset = mtx::requests::Preset::PrivateChat; req.preset = mtx::requests::Preset::PrivateChat;
req.visibility = mtx::requests::Visibility::Private; req.visibility = mtx::requests::Visibility::Private;
if (utils::localUser() != this->userId) if (utils::localUser() != this->userid_)
req.invite = {this->userId.toStdString()}; req.invite = {this->userid_.toStdString()};
emit ChatPage::instance()->createRoom(req); emit ChatPage::instance()->createRoom(req);
} }

View file

@ -1,34 +1,92 @@
#pragma once #pragma once
#include <QAbstractListModel>
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QVector> #include <QVector>
#include "MatrixClient.h" #include "MatrixClient.h"
class DeviceInfo; namespace verification {
Q_NAMESPACE
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
public:
enum Roles
{
DeviceId,
DeviceName,
VerificationStatus,
};
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 class UserProfile : public QObject
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString userId READ getUserId WRITE setUserId NOTIFY userIdChanged) Q_PROPERTY(QString displayName READ displayName CONSTANT)
Q_PROPERTY(std::vector<DeviceInfo> deviceList READ getDeviceList NOTIFY deviceListUpdated) Q_PROPERTY(QString userid READ userid CONSTANT)
Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT)
public: public:
// constructor UserProfile(QString roomid, QString userid, QObject *parent = 0);
explicit UserProfile(QObject *parent = 0);
// getters
std::vector<DeviceInfo> getDeviceList();
QString getUserId();
// setters
void setUserId(const QString &userId);
enum Status DeviceInfoModel *deviceList();
{
VERIFIED, QString userid();
UNVERIFIED, QString displayName();
BLOCKED QString avatarUrl();
};
Q_ENUM(Status)
void fetchDeviceList(const QString &userID); void fetchDeviceList(const QString &userID);
Q_INVOKABLE void banUser(); Q_INVOKABLE void banUser();
@ -36,37 +94,13 @@ public:
Q_INVOKABLE void kickUser(); Q_INVOKABLE void kickUser();
Q_INVOKABLE void startChat(); 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: private:
std::vector<DeviceInfo> deviceList; QString roomid_, userid_;
QString userId;
std::optional<std::string> cross_verified; std::optional<std::string> cross_verified;
DeviceInfoModel deviceList_;
void callback_fn(const mtx::responses::QueryKeys &res, void callback_fn(const mtx::responses::QueryKeys &res,
mtx::http::RequestErr err, mtx::http::RequestErr err,
std::string user_id, std::string user_id,
std::optional<std::vector<std::string>> cross_verified); 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;
};

View file

@ -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);
}

View file

@ -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;
};