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

View File

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

View File

@ -8,220 +8,211 @@ import im.nheko 1.0
import "./device-verification"
ApplicationWindow{
property var user_data
property var avatarUrl
property var colors: currentActivePalette
property var profile
id:userProfileDialog
height: 650
width: 420
modality:Qt.WindowModal
Layout.alignment: Qt.AlignHCenter
palette: colors
id: userProfileDialog
height: 650
width: 420
modality: Qt.WindowModal
Layout.alignment: Qt.AlignHCenter
palette: colors
Component {
Component {
id: deviceVerificationDialog
DeviceVerification {}
}
Component{
id: deviceVerificationFlow
DeviceVerificationFlow {}
}
Component{
id: deviceVerificationFlow
DeviceVerificationFlow {}
}
background: Item{
id: userProfileItem
width: userProfileDialog.width
height: userProfileDialog.height
background: Item{
id: userProfileItem
width: userProfileDialog.width
height: userProfileDialog.height
// Layout.fillHeight : true
// Layout.fillHeight : true
ColumnLayout{
anchors.fill: userProfileItem
width: userProfileDialog.width
spacing: 10
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
}
}
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{
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 {
text: profile.displayName
fontSizeMode: Text.HorizontalFit
font.pixelSize: 20
color: TimelineManager.userColor(profile.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
}
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 : {
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()
}
}
}
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
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
ListView{
id: devicelist
anchors.fill: parent
clip: true
spacing: 4
model: UserProfileModel{
id: modelDeviceList
deviceList.userId : user_data.userId
}
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: 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
}
}
}
}
}
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
Button{
id: okbutton
text:"OK"
onClicked: userProfileDialog.close()
Layout.margins : {
right : 10
bottom: 5
}
Layout.alignment: Qt.AlignRight | Qt.AlignBottom
palette {
button: "white"
}
Layout.margins : {
right : 10
bottom: 5
}
contentItem: Text {
text: okbutton.text
color: "black"
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
}
}
palette {
button: "white"
}
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");
}
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)
{

View File

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

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
TimelineModel::openUserProfile(QString userid) const
TimelineModel::openUserProfile(QString userid)
{
MainWindow::instance()->openUserProfile(userid, room_id_);
emit openProfile(new UserProfile(room_id_, userid, this));
}
void

View File

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

View File

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

View File

@ -5,47 +5,72 @@
#include "Utils.h"
#include "mtx/responses/crypto.hpp"
#include <iostream> // only for debugging
Q_DECLARE_METATYPE(UserProfile::Status)
UserProfile::UserProfile(QObject *parent)
UserProfile::UserProfile(QString roomid, QString userid, QObject *parent)
: QObject(parent)
, roomid_(roomid)
, userid_(userid)
{
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});
});
fetchDeviceList(this->userid_);
}
std::vector<DeviceInfo>
UserProfile::getDeviceList()
QHash<int, QByteArray>
DeviceInfoModel::roleNames() const
{
return this->deviceList;
return {
{DeviceId, "deviceId"},
{DeviceName, "deviceName"},
{VerificationStatus, "verificationStatus"},
};
}
QString
UserProfile::getUserId()
QVariant
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
UserProfile::setUserId(const QString &user_id)
DeviceInfoModel::reset(const std::vector<DeviceInfo> &deviceList)
{
if (this->userId != userId)
return;
else {
this->userId = user_id;
emit UserProfile::userIdChanged();
}
beginResetModel();
this->deviceList_ = std::move(deviceList);
endResetModel();
}
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
@ -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);
}

View File

@ -1,34 +1,92 @@
#pragma once
#include <QAbstractListModel>
#include <QObject>
#include <QString>
#include <QVector>
#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
{
Q_OBJECT
Q_PROPERTY(QString userId READ getUserId WRITE setUserId NOTIFY userIdChanged)
Q_PROPERTY(std::vector<DeviceInfo> deviceList READ getDeviceList NOTIFY deviceListUpdated)
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:
// constructor
explicit UserProfile(QObject *parent = 0);
// getters
std::vector<DeviceInfo> getDeviceList();
QString getUserId();
// setters
void setUserId(const QString &userId);
UserProfile(QString roomid, QString userid, QObject *parent = 0);
enum Status
{
VERIFIED,
UNVERIFIED,
BLOCKED
};
Q_ENUM(Status)
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;
};

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