Show device list in user profile & add option to create 1-1 chat
Before Width: | Height: | Size: 965 B After Width: | Height: | Size: 375 B |
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 414 B |
Before Width: | Height: | Size: 596 B After Width: | Height: | Size: 627 B |
Before Width: | Height: | Size: 872 B After Width: | Height: | Size: 1,005 B |
Before Width: | Height: | Size: 1.3 KiB After Width: | Height: | Size: 761 B |
Before Width: | Height: | Size: 2.5 KiB After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 830 B After Width: | Height: | Size: 674 B |
Before Width: | Height: | Size: 1 KiB After Width: | Height: | Size: 892 B |
|
@ -80,6 +80,7 @@ public:
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
void leaveRoom(const QString &room_id);
|
void leaveRoom(const QString &room_id);
|
||||||
|
void createRoom(const mtx::requests::CreateRoom &req);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void connectionLost();
|
void connectionLost();
|
||||||
|
@ -159,7 +160,6 @@ private slots:
|
||||||
void dropToLoginPage(const QString &msg);
|
void dropToLoginPage(const QString &msg);
|
||||||
|
|
||||||
void joinRoom(const QString &room);
|
void joinRoom(const QString &room);
|
||||||
void createRoom(const mtx::requests::CreateRoom &req);
|
|
||||||
void sendTypingNotifications();
|
void sendTypingNotifications();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
|
@ -317,6 +317,7 @@ MainWindow::openUserProfile(const QString &user_id, const QString &room_id)
|
||||||
|
|
||||||
userProfileModal_ =
|
userProfileModal_ =
|
||||||
QSharedPointer<OverlayModal>(new OverlayModal(this, userProfileDialog_.data()));
|
QSharedPointer<OverlayModal>(new OverlayModal(this, userProfileDialog_.data()));
|
||||||
|
userProfileModal_->setContentAlignment(Qt::AlignTop | Qt::AlignHCenter);
|
||||||
|
|
||||||
userProfileModal_->show();
|
userProfileModal_->show();
|
||||||
}
|
}
|
||||||
|
@ -394,7 +395,6 @@ MainWindow::showOverlayProgressBar()
|
||||||
progressModal_ =
|
progressModal_ =
|
||||||
QSharedPointer<OverlayModal>(new OverlayModal(this, spinner_.data()),
|
QSharedPointer<OverlayModal>(new OverlayModal(this, spinner_.data()),
|
||||||
[](OverlayModal *modal) { modal->deleteLater(); });
|
[](OverlayModal *modal) { modal->deleteLater(); });
|
||||||
progressModal_->setContentAlignment(Qt::AlignCenter);
|
|
||||||
progressModal_->setColor(QColor(30, 30, 30));
|
progressModal_->setColor(QColor(30, 30, 30));
|
||||||
progressModal_->setDismissible(false);
|
progressModal_->setDismissible(false);
|
||||||
progressModal_->show();
|
progressModal_->show();
|
||||||
|
|
|
@ -2,11 +2,19 @@
|
||||||
|
|
||||||
#include <QApplication>
|
#include <QApplication>
|
||||||
#include <QDesktopWidget>
|
#include <QDesktopWidget>
|
||||||
|
#include <QSettings>
|
||||||
|
|
||||||
#include <variant.hpp>
|
#include <variant.hpp>
|
||||||
|
|
||||||
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
||||||
|
|
||||||
|
QString
|
||||||
|
utils::localUser()
|
||||||
|
{
|
||||||
|
QSettings settings;
|
||||||
|
return settings.value("auth/user_id").toString();
|
||||||
|
}
|
||||||
|
|
||||||
QString
|
QString
|
||||||
utils::descriptiveTime(const QDateTime &then)
|
utils::descriptiveTime(const QDateTime &then)
|
||||||
{
|
{
|
||||||
|
|
|
@ -15,6 +15,9 @@ namespace utils {
|
||||||
|
|
||||||
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
using TimelineEvent = mtx::events::collections::TimelineEvents;
|
||||||
|
|
||||||
|
QString
|
||||||
|
localUser();
|
||||||
|
|
||||||
//! Human friendly timestamp representation.
|
//! Human friendly timestamp representation.
|
||||||
QString
|
QString
|
||||||
descriptiveTime(const QDateTime &then);
|
descriptiveTime(const QDateTime &then);
|
||||||
|
|
|
@ -8,6 +8,8 @@
|
||||||
|
|
||||||
#include "AvatarProvider.h"
|
#include "AvatarProvider.h"
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
|
#include "ChatPage.h"
|
||||||
|
#include "MatrixClient.h"
|
||||||
#include "Utils.h"
|
#include "Utils.h"
|
||||||
#include "dialogs/UserProfile.h"
|
#include "dialogs/UserProfile.h"
|
||||||
#include "ui/Avatar.h"
|
#include "ui/Avatar.h"
|
||||||
|
@ -17,10 +19,25 @@ using namespace dialogs;
|
||||||
|
|
||||||
constexpr int BUTTON_SIZE = 36;
|
constexpr int BUTTON_SIZE = 36;
|
||||||
|
|
||||||
DeviceItem::DeviceItem(QWidget *parent, QString deviceName)
|
DeviceItem::DeviceItem(DeviceInfo device, QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
, name_(deviceName)
|
, 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)
|
UserProfile::UserProfile(QWidget *parent)
|
||||||
: QWidget(parent)
|
: QWidget(parent)
|
||||||
|
@ -34,6 +51,7 @@ UserProfile::UserProfile(QWidget *parent)
|
||||||
banBtn_->setIcon(banIcon);
|
banBtn_->setIcon(banIcon);
|
||||||
banBtn_->setIconSize(QSize(BUTTON_SIZE / 2, BUTTON_SIZE / 2));
|
banBtn_->setIconSize(QSize(BUTTON_SIZE / 2, BUTTON_SIZE / 2));
|
||||||
banBtn_->setToolTip(tr("Ban the user from the room"));
|
banBtn_->setToolTip(tr("Ban the user from the room"));
|
||||||
|
banBtn_->setDisabled(true); // Not used yet.
|
||||||
|
|
||||||
ignoreIcon.addFile(":/icons/icons/ui/volume-off-indicator.png");
|
ignoreIcon.addFile(":/icons/icons/ui/volume-off-indicator.png");
|
||||||
ignoreBtn_ = new FlatButton(this);
|
ignoreBtn_ = new FlatButton(this);
|
||||||
|
@ -42,6 +60,7 @@ UserProfile::UserProfile(QWidget *parent)
|
||||||
ignoreBtn_->setIcon(ignoreIcon);
|
ignoreBtn_->setIcon(ignoreIcon);
|
||||||
ignoreBtn_->setIconSize(QSize(BUTTON_SIZE / 2, BUTTON_SIZE / 2));
|
ignoreBtn_->setIconSize(QSize(BUTTON_SIZE / 2, BUTTON_SIZE / 2));
|
||||||
ignoreBtn_->setToolTip(tr("Ignore messages from this user"));
|
ignoreBtn_->setToolTip(tr("Ignore messages from this user"));
|
||||||
|
ignoreBtn_->setDisabled(true); // Not used yet.
|
||||||
|
|
||||||
kickIcon.addFile(":/icons/icons/ui/round-remove-button.png");
|
kickIcon.addFile(":/icons/icons/ui/round-remove-button.png");
|
||||||
kickBtn_ = new FlatButton(this);
|
kickBtn_ = new FlatButton(this);
|
||||||
|
@ -50,6 +69,7 @@ UserProfile::UserProfile(QWidget *parent)
|
||||||
kickBtn_->setIcon(kickIcon);
|
kickBtn_->setIcon(kickIcon);
|
||||||
kickBtn_->setIconSize(QSize(BUTTON_SIZE / 2, BUTTON_SIZE / 2));
|
kickBtn_->setIconSize(QSize(BUTTON_SIZE / 2, BUTTON_SIZE / 2));
|
||||||
kickBtn_->setToolTip(tr("Kick the user from the room"));
|
kickBtn_->setToolTip(tr("Kick the user from the room"));
|
||||||
|
kickBtn_->setDisabled(true); // Not used yet.
|
||||||
|
|
||||||
startChatIcon.addFile(":/icons/icons/ui/black-bubble-speech.png");
|
startChatIcon.addFile(":/icons/icons/ui/black-bubble-speech.png");
|
||||||
startChat_ = new FlatButton(this);
|
startChat_ = new FlatButton(this);
|
||||||
|
@ -59,21 +79,34 @@ UserProfile::UserProfile(QWidget *parent)
|
||||||
startChat_->setIconSize(QSize(BUTTON_SIZE / 2, BUTTON_SIZE / 2));
|
startChat_->setIconSize(QSize(BUTTON_SIZE / 2, BUTTON_SIZE / 2));
|
||||||
startChat_->setToolTip(tr("Start a conversation"));
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
// Button line
|
// Button line
|
||||||
auto btnLayout = new QHBoxLayout;
|
auto btnLayout = new QHBoxLayout;
|
||||||
|
btnLayout->addStretch(1);
|
||||||
btnLayout->addWidget(startChat_);
|
btnLayout->addWidget(startChat_);
|
||||||
btnLayout->addWidget(ignoreBtn_);
|
btnLayout->addWidget(ignoreBtn_);
|
||||||
|
|
||||||
// TODO: check if the user has enough power level given the room_id
|
|
||||||
// in which the profile was opened.
|
|
||||||
btnLayout->addWidget(kickBtn_);
|
btnLayout->addWidget(kickBtn_);
|
||||||
btnLayout->addWidget(banBtn_);
|
btnLayout->addWidget(banBtn_);
|
||||||
|
btnLayout->addStretch(1);
|
||||||
btnLayout->setSpacing(8);
|
btnLayout->setSpacing(8);
|
||||||
btnLayout->setMargin(0);
|
btnLayout->setMargin(0);
|
||||||
|
|
||||||
avatar_ = new Avatar(this);
|
avatar_ = new Avatar(this);
|
||||||
avatar_->setLetter("X");
|
avatar_->setLetter("X");
|
||||||
avatar_->setSize(148);
|
avatar_->setSize(128);
|
||||||
|
|
||||||
QFont font;
|
QFont font;
|
||||||
font.setPointSizeF(font.pointSizeF() * 2);
|
font.setPointSizeF(font.pointSizeF() * 2);
|
||||||
|
@ -90,10 +123,26 @@ UserProfile::UserProfile(QWidget *parent)
|
||||||
textLayout->setSpacing(4);
|
textLayout->setSpacing(4);
|
||||||
textLayout->setMargin(0);
|
textLayout->setMargin(0);
|
||||||
|
|
||||||
|
devices_ = new QListWidget{this};
|
||||||
|
devices_->setFrameStyle(QFrame::NoFrame);
|
||||||
|
devices_->setSelectionMode(QAbstractItemView::NoSelection);
|
||||||
|
devices_->setAttribute(Qt::WA_MacShowFocusRect, 0);
|
||||||
|
devices_->setSpacing(5);
|
||||||
|
devices_->hide();
|
||||||
|
|
||||||
|
QFont descriptionLabelFont;
|
||||||
|
descriptionLabelFont.setWeight(65);
|
||||||
|
|
||||||
|
devicesLabel_ = new QLabel(tr("Devices").toUpper(), this);
|
||||||
|
devicesLabel_->setFont(descriptionLabelFont);
|
||||||
|
devicesLabel_->hide();
|
||||||
|
|
||||||
auto vlayout = new QVBoxLayout{this};
|
auto vlayout = new QVBoxLayout{this};
|
||||||
vlayout->addWidget(avatar_);
|
vlayout->addWidget(avatar_);
|
||||||
vlayout->addLayout(textLayout);
|
vlayout->addLayout(textLayout);
|
||||||
vlayout->addLayout(btnLayout);
|
vlayout->addLayout(btnLayout);
|
||||||
|
vlayout->addWidget(devicesLabel_, Qt::AlignLeft);
|
||||||
|
vlayout->addWidget(devices_);
|
||||||
|
|
||||||
vlayout->setAlignment(avatar_, Qt::AlignCenter | Qt::AlignTop);
|
vlayout->setAlignment(avatar_, Qt::AlignCenter | Qt::AlignTop);
|
||||||
vlayout->setAlignment(userIdLabel_, Qt::AlignCenter | Qt::AlignTop);
|
vlayout->setAlignment(userIdLabel_, Qt::AlignCenter | Qt::AlignTop);
|
||||||
|
@ -107,6 +156,10 @@ UserProfile::UserProfile(QWidget *parent)
|
||||||
|
|
||||||
vlayout->setSpacing(15);
|
vlayout->setSpacing(15);
|
||||||
vlayout->setContentsMargins(20, 40, 20, 20);
|
vlayout->setContentsMargins(20, 40, 20, 20);
|
||||||
|
|
||||||
|
qRegisterMetaType<std::vector<DeviceInfo>>();
|
||||||
|
|
||||||
|
connect(this, &UserProfile::devicesRetrieved, this, &UserProfile::updateDeviceList);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
@ -121,13 +174,7 @@ UserProfile::init(const QString &userId, const QString &roomId)
|
||||||
AvatarProvider::resolve(
|
AvatarProvider::resolve(
|
||||||
roomId, userId, this, [this](const QImage &img) { avatar_->setImage(img); });
|
roomId, userId, this, [this](const QImage &img) { avatar_->setImage(img); });
|
||||||
|
|
||||||
QSettings settings;
|
auto localUser = utils::localUser();
|
||||||
auto localUser = settings.value("auth/user_id").toString();
|
|
||||||
|
|
||||||
if (localUser == userId) {
|
|
||||||
qDebug() << "the local user should have edit rights on avatar & display name";
|
|
||||||
// TODO: click on display name & avatar to change.
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
bool hasMemberRights =
|
bool hasMemberRights =
|
||||||
|
@ -141,6 +188,75 @@ UserProfile::init(const QString &userId, const QString &roomId)
|
||||||
} catch (const lmdb::error &e) {
|
} catch (const lmdb::error &e) {
|
||||||
nhlog::db()->warn("lmdb error: {}", e.what());
|
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()] = {};
|
||||||
|
|
||||||
|
http::client()->query_keys(
|
||||||
|
req,
|
||||||
|
[user_id = userId.toStdString(), 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())
|
||||||
|
emit devicesRetrieved(deviceInfo);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
UserProfile::updateDeviceList(const std::vector<DeviceInfo> &devices)
|
||||||
|
{
|
||||||
|
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
|
|
|
@ -7,6 +7,15 @@ class Avatar;
|
||||||
class FlatButton;
|
class FlatButton;
|
||||||
class QLabel;
|
class QLabel;
|
||||||
class QListWidget;
|
class QListWidget;
|
||||||
|
class Toggle;
|
||||||
|
|
||||||
|
struct DeviceInfo
|
||||||
|
{
|
||||||
|
QString device_id;
|
||||||
|
QString display_name;
|
||||||
|
};
|
||||||
|
|
||||||
|
Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
|
||||||
|
|
||||||
namespace dialogs {
|
namespace dialogs {
|
||||||
|
|
||||||
|
@ -15,10 +24,10 @@ class DeviceItem : public QWidget
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit DeviceItem(QWidget *parent, QString deviceName);
|
explicit DeviceItem(DeviceInfo device, QWidget *parent);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QString name_;
|
DeviceInfo info_;
|
||||||
|
|
||||||
// Toggle *verifyToggle_;
|
// Toggle *verifyToggle_;
|
||||||
};
|
};
|
||||||
|
@ -34,12 +43,15 @@ public:
|
||||||
protected:
|
protected:
|
||||||
void paintEvent(QPaintEvent *) override;
|
void paintEvent(QPaintEvent *) override;
|
||||||
|
|
||||||
|
signals:
|
||||||
|
void devicesRetrieved(const std::vector<DeviceInfo> &devices);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void updateDeviceList(const std::vector<DeviceInfo> &devices);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Avatar *avatar_;
|
Avatar *avatar_;
|
||||||
|
|
||||||
QString displayName_;
|
|
||||||
QString userId_;
|
|
||||||
|
|
||||||
QLabel *userIdLabel_;
|
QLabel *userIdLabel_;
|
||||||
QLabel *displayNameLabel_;
|
QLabel *displayNameLabel_;
|
||||||
|
|
||||||
|
@ -48,6 +60,8 @@ private:
|
||||||
FlatButton *ignoreBtn_;
|
FlatButton *ignoreBtn_;
|
||||||
FlatButton *startChat_;
|
FlatButton *startChat_;
|
||||||
|
|
||||||
|
QLabel *devicesLabel_;
|
||||||
QListWidget *devices_;
|
QListWidget *devices_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // dialogs
|
} // dialogs
|
||||||
|
|
|
@ -29,7 +29,7 @@ OverlayModal::OverlayModal(QWidget *parent, QWidget *content)
|
||||||
layout_->addWidget(content);
|
layout_->addWidget(content);
|
||||||
layout_->setSpacing(0);
|
layout_->setSpacing(0);
|
||||||
layout_->setContentsMargins(10, 40, 10, 20);
|
layout_->setContentsMargins(10, 40, 10, 20);
|
||||||
setContentAlignment(Qt::AlignTop | Qt::AlignHCenter);
|
setContentAlignment(Qt::AlignCenter);
|
||||||
|
|
||||||
content->setFocus();
|
content->setFocus();
|
||||||
}
|
}
|
||||||
|
|