matrixion/src/dialogs/UserProfile.cpp

304 lines
11 KiB
C++
Raw Normal View History

2018-07-20 12:02:35 +03:00
#include <QHBoxLayout>
#include <QLabel>
#include <QListWidget>
#include <QPaintEvent>
#include <QSettings>
#include <QStyleOption>
#include <QVBoxLayout>
#include "AvatarProvider.h"
#include "Cache.h"
#include "ChatPage.h"
#include "MatrixClient.h"
2018-07-20 12:02:35 +03:00
#include "Utils.h"
#include "dialogs/UserProfile.h"
#include "ui/Avatar.h"
#include "ui/FlatButton.h"
using namespace dialogs;
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;
2018-07-20 12:02:35 +03:00
DeviceItem::DeviceItem(DeviceInfo device, QWidget *parent)
2018-07-20 12:02:35 +03:00
: 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);
}
2018-07-20 12:02:35 +03:00
UserProfile::UserProfile(QWidget *parent)
: QWidget(parent)
{
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);
2018-07-20 12:02:35 +03:00
banBtn_->setIcon(banIcon);
banBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS));
2018-07-20 12:02:35 +03:00
banBtn_->setToolTip(tr("Ban the user from the room"));
banBtn_->setDisabled(true); // Not used yet.
2018-07-20 12:02:35 +03:00
ignoreIcon.addFile(":/icons/icons/ui/volume-off-indicator.png");
ignoreBtn_ = new FlatButton(this);
ignoreBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE);
ignoreBtn_->setCornerRadius(BUTTON_RADIUS);
2018-07-20 12:02:35 +03:00
ignoreBtn_->setIcon(ignoreIcon);
ignoreBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS));
2018-07-20 12:02:35 +03:00
ignoreBtn_->setToolTip(tr("Ignore messages from this user"));
ignoreBtn_->setDisabled(true); // Not used yet.
2018-07-20 12:02:35 +03:00
kickIcon.addFile(":/icons/icons/ui/round-remove-button.png");
kickBtn_ = new FlatButton(this);
kickBtn_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE);
kickBtn_->setCornerRadius(BUTTON_RADIUS);
2018-07-20 12:02:35 +03:00
kickBtn_->setIcon(kickIcon);
kickBtn_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS));
2018-07-20 12:02:35 +03:00
kickBtn_->setToolTip(tr("Kick the user from the room"));
kickBtn_->setDisabled(true); // Not used yet.
2018-07-20 12:02:35 +03:00
startChatIcon.addFile(":/icons/icons/ui/black-bubble-speech.png");
startChat_ = new FlatButton(this);
startChat_->setFixedSize(BUTTON_SIZE, BUTTON_SIZE);
startChat_->setCornerRadius(BUTTON_RADIUS);
2018-07-20 12:02:35 +03:00
startChat_->setIcon(startChatIcon);
startChat_->setIconSize(QSize(BUTTON_RADIUS, BUTTON_RADIUS));
2018-07-20 12:02:35 +03:00
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);
});
2018-07-20 12:02:35 +03:00
// Button line
auto btnLayout = new QHBoxLayout;
btnLayout->addStretch(1);
2018-07-20 12:02:35 +03:00
btnLayout->addWidget(startChat_);
btnLayout->addWidget(ignoreBtn_);
btnLayout->addWidget(kickBtn_);
btnLayout->addWidget(banBtn_);
btnLayout->addStretch(1);
2018-07-20 12:02:35 +03:00
btnLayout->setSpacing(8);
btnLayout->setMargin(0);
avatar_ = new Avatar(this);
avatar_->setLetter("X");
avatar_->setSize(128);
2018-07-20 12:02:35 +03:00
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);
2018-07-20 12:02:35 +03:00
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);
devices_->hide();
QFont descriptionLabelFont;
descriptionLabelFont.setWeight(65);
devicesLabel_ = new QLabel(tr("Devices").toUpper(), this);
devicesLabel_->setFont(descriptionLabelFont);
devicesLabel_->hide();
2018-07-20 12:02:35 +03:00
auto vlayout = new QVBoxLayout{this};
vlayout->addWidget(avatar_);
vlayout->addLayout(textLayout);
vlayout->addLayout(btnLayout);
vlayout->addWidget(devicesLabel_, Qt::AlignLeft);
vlayout->addWidget(devices_);
vlayout->addStretch(1);
2018-07-20 12:02:35 +03:00
vlayout->setAlignment(avatar_, Qt::AlignCenter | Qt::AlignTop);
vlayout->setAlignment(userIdLabel_, Qt::AlignCenter | Qt::AlignTop);
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
QFont doubleFont;
doubleFont.setPointSizeF(doubleFont.pointSizeF() * 2);
setMinimumWidth(
std::max(devices_->sizeHint().width() + 4 * WIDGET_MARGIN,
QFontMetrics(doubleFont).averageCharWidth() * 30 - 2 * WIDGET_MARGIN));
2018-07-20 12:02:35 +03:00
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
vlayout->setSpacing(WIDGET_SPACING);
vlayout->setContentsMargins(WIDGET_MARGIN, TOP_WIDGET_MARGIN, WIDGET_MARGIN, WIDGET_MARGIN);
qRegisterMetaType<std::vector<DeviceInfo>>();
2018-07-20 12:02:35 +03:00
}
void
UserProfile::resetToDefaults()
{
avatar_->setLetter("X");
devices_->clear();
ignoreBtn_->show();
devices_->hide();
devicesLabel_->hide();
}
2018-07-20 12:02:35 +03:00
void
UserProfile::init(const QString &userId, const QString &roomId)
{
resetToDefaults();
2018-07-20 12:02:35 +03:00
auto displayName = Cache::displayName(roomId, userId);
userIdLabel_->setText(userId);
displayNameLabel_->setText(displayName);
avatar_->setLetter(utils::firstChar(displayName));
AvatarProvider::resolve(
roomId, userId, this, [this](const QImage &img) { avatar_->setImage(img); });
auto localUser = utils::localUser();
2018-07-20 12:02:35 +03:00
try {
bool hasMemberRights =
cache::client()->hasEnoughPowerLevel({mtx::events::EventType::RoomMember},
roomId.toStdString(),
localUser.toStdString());
if (!hasMemberRights) {
kickBtn_->hide();
banBtn_->hide();
} else {
kickBtn_->show();
banBtn_->show();
2018-07-20 12:02:35 +03:00
}
} 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()] = {};
2018-08-11 13:50:56 +03:00
// 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,
2018-08-11 13:50:56 +03:00
[user_id = userId.toStdString(), proxy = std::move(proxy), this](
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())
2018-08-11 13:50:56 +03:00
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();
2018-07-20 12:02:35 +03:00
}
void
UserProfile::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}