summary refs log tree commit diff
path: root/src/dialogs
diff options
context:
space:
mode:
authorCH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>2020-08-30 22:27:14 +0530
committerCH Chethan Reddy <40890937+Chethan2k1@users.noreply.github.com>2020-08-30 22:27:14 +0530
commitb174bd938084a60fde5030dfc4dd0067a39f5a3f (patch)
tree6f2bb24fb2ba01adb2957aea67aecda501aa41ac /src/dialogs
parentChange the tag for mtxclient (diff)
parentMerge pull request #265 from trilene/voip (diff)
downloadnheko-b174bd938084a60fde5030dfc4dd0067a39f5a3f.tar.xz
Merge remote-tracking branch 'upstream/master' into device-verification
Diffstat (limited to 'src/dialogs')
-rw-r--r--src/dialogs/AcceptCall.cpp134
-rw-r--r--src/dialogs/AcceptCall.h36
-rw-r--r--src/dialogs/PlaceCall.cpp103
-rw-r--r--src/dialogs/PlaceCall.h36
-rw-r--r--src/dialogs/UserProfile.cpp316
-rw-r--r--src/dialogs/UserProfile.h70
6 files changed, 695 insertions, 0 deletions
diff --git a/src/dialogs/AcceptCall.cpp b/src/dialogs/AcceptCall.cpp
new file mode 100644

index 00000000..2b47b7dc --- /dev/null +++ b/src/dialogs/AcceptCall.cpp
@@ -0,0 +1,134 @@ +#include <QComboBox> +#include <QLabel> +#include <QPushButton> +#include <QString> +#include <QVBoxLayout> + +#include "ChatPage.h" +#include "Config.h" +#include "UserSettingsPage.h" +#include "Utils.h" +#include "WebRTCSession.h" +#include "dialogs/AcceptCall.h" +#include "ui/Avatar.h" + +namespace dialogs { + +AcceptCall::AcceptCall(const QString &caller, + const QString &displayName, + const QString &roomName, + const QString &avatarUrl, + QSharedPointer<UserSettings> settings, + QWidget *parent) + : QWidget(parent) +{ + std::string errorMessage; + if (!WebRTCSession::instance().init(&errorMessage)) { + emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); + emit close(); + return; + } + audioDevices_ = WebRTCSession::instance().getAudioSourceNames( + settings->defaultAudioSource().toStdString()); + if (audioDevices_.empty()) { + emit ChatPage::instance()->showNotification( + "Incoming call: No audio sources found."); + emit close(); + return; + } + + setAutoFillBackground(true); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose, true); + + setMinimumWidth(conf::modals::MIN_WIDGET_WIDTH); + setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum); + + auto layout = new QVBoxLayout(this); + layout->setSpacing(conf::modals::WIDGET_SPACING); + layout->setMargin(conf::modals::WIDGET_MARGIN); + + QFont f; + f.setPointSizeF(f.pointSizeF()); + + QFont labelFont; + labelFont.setWeight(QFont::Medium); + + QLabel *displayNameLabel = nullptr; + if (!displayName.isEmpty() && displayName != caller) { + displayNameLabel = new QLabel(displayName, this); + labelFont.setPointSizeF(f.pointSizeF() * 2); + displayNameLabel->setFont(labelFont); + displayNameLabel->setAlignment(Qt::AlignCenter); + } + + QLabel *callerLabel = new QLabel(caller, this); + labelFont.setPointSizeF(f.pointSizeF() * 1.2); + callerLabel->setFont(labelFont); + callerLabel->setAlignment(Qt::AlignCenter); + + auto avatar = new Avatar(this, QFontMetrics(f).height() * 6); + if (!avatarUrl.isEmpty()) + avatar->setImage(avatarUrl); + else + avatar->setLetter(utils::firstChar(roomName)); + + const int iconSize = 22; + QLabel *callTypeIndicator = new QLabel(this); + callTypeIndicator->setPixmap( + QIcon(":/icons/icons/ui/place-call.png").pixmap(QSize(iconSize * 2, iconSize * 2))); + + QLabel *callTypeLabel = new QLabel("Voice Call", this); + labelFont.setPointSizeF(f.pointSizeF() * 1.1); + callTypeLabel->setFont(labelFont); + callTypeLabel->setAlignment(Qt::AlignCenter); + + auto buttonLayout = new QHBoxLayout; + buttonLayout->setSpacing(18); + acceptBtn_ = new QPushButton(tr("Accept"), this); + acceptBtn_->setDefault(true); + acceptBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png")); + acceptBtn_->setIconSize(QSize(iconSize, iconSize)); + + rejectBtn_ = new QPushButton(tr("Reject"), this); + rejectBtn_->setIcon(QIcon(":/icons/icons/ui/end-call.png")); + rejectBtn_->setIconSize(QSize(iconSize, iconSize)); + buttonLayout->addWidget(acceptBtn_); + buttonLayout->addWidget(rejectBtn_); + + auto deviceLayout = new QHBoxLayout; + auto audioLabel = new QLabel(this); + audioLabel->setPixmap( + QIcon(":/icons/icons/ui/microphone-unmute.png").pixmap(QSize(iconSize, iconSize))); + + auto deviceList = new QComboBox(this); + for (const auto &d : audioDevices_) + deviceList->addItem(QString::fromStdString(d)); + + deviceLayout->addStretch(); + deviceLayout->addWidget(audioLabel); + deviceLayout->addWidget(deviceList); + + if (displayNameLabel) + layout->addWidget(displayNameLabel, 0, Qt::AlignCenter); + layout->addWidget(callerLabel, 0, Qt::AlignCenter); + layout->addWidget(avatar, 0, Qt::AlignCenter); + layout->addWidget(callTypeIndicator, 0, Qt::AlignCenter); + layout->addWidget(callTypeLabel, 0, Qt::AlignCenter); + layout->addLayout(buttonLayout); + layout->addLayout(deviceLayout); + + connect(acceptBtn_, &QPushButton::clicked, this, [this, deviceList, settings]() { + WebRTCSession::instance().setAudioSource(deviceList->currentIndex()); + settings->setDefaultAudioSource( + QString::fromStdString(audioDevices_[deviceList->currentIndex()])); + emit accept(); + emit close(); + }); + connect(rejectBtn_, &QPushButton::clicked, this, [this]() { + emit reject(); + emit close(); + }); +} +} diff --git a/src/dialogs/AcceptCall.h b/src/dialogs/AcceptCall.h new file mode 100644
index 00000000..5db8fcfa --- /dev/null +++ b/src/dialogs/AcceptCall.h
@@ -0,0 +1,36 @@ +#pragma once + +#include <string> +#include <vector> + +#include <QSharedPointer> +#include <QWidget> + +class QPushButton; +class QString; +class UserSettings; + +namespace dialogs { + +class AcceptCall : public QWidget +{ + Q_OBJECT + +public: + AcceptCall(const QString &caller, + const QString &displayName, + const QString &roomName, + const QString &avatarUrl, + QSharedPointer<UserSettings> settings, + QWidget *parent = nullptr); + +signals: + void accept(); + void reject(); + +private: + QPushButton *acceptBtn_; + QPushButton *rejectBtn_; + std::vector<std::string> audioDevices_; +}; +} diff --git a/src/dialogs/PlaceCall.cpp b/src/dialogs/PlaceCall.cpp new file mode 100644
index 00000000..8acdbe88 --- /dev/null +++ b/src/dialogs/PlaceCall.cpp
@@ -0,0 +1,103 @@ +#include <QComboBox> +#include <QLabel> +#include <QPushButton> +#include <QString> +#include <QVBoxLayout> + +#include "ChatPage.h" +#include "Config.h" +#include "UserSettingsPage.h" +#include "Utils.h" +#include "WebRTCSession.h" +#include "dialogs/PlaceCall.h" +#include "ui/Avatar.h" + +namespace dialogs { + +PlaceCall::PlaceCall(const QString &callee, + const QString &displayName, + const QString &roomName, + const QString &avatarUrl, + QSharedPointer<UserSettings> settings, + QWidget *parent) + : QWidget(parent) +{ + std::string errorMessage; + if (!WebRTCSession::instance().init(&errorMessage)) { + emit ChatPage::instance()->showNotification(QString::fromStdString(errorMessage)); + emit close(); + return; + } + audioDevices_ = WebRTCSession::instance().getAudioSourceNames( + settings->defaultAudioSource().toStdString()); + if (audioDevices_.empty()) { + emit ChatPage::instance()->showNotification("No audio sources found."); + emit close(); + return; + } + + setAutoFillBackground(true); + setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint); + setWindowModality(Qt::WindowModal); + setAttribute(Qt::WA_DeleteOnClose, true); + + auto layout = new QVBoxLayout(this); + layout->setSpacing(conf::modals::WIDGET_SPACING); + layout->setMargin(conf::modals::WIDGET_MARGIN); + + auto buttonLayout = new QHBoxLayout; + buttonLayout->setSpacing(15); + buttonLayout->setMargin(0); + + QFont f; + f.setPointSizeF(f.pointSizeF()); + auto avatar = new Avatar(this, QFontMetrics(f).height() * 3); + if (!avatarUrl.isEmpty()) + avatar->setImage(avatarUrl); + else + avatar->setLetter(utils::firstChar(roomName)); + const int iconSize = 18; + voiceBtn_ = new QPushButton(tr("Voice"), this); + voiceBtn_->setIcon(QIcon(":/icons/icons/ui/place-call.png")); + voiceBtn_->setIconSize(QSize(iconSize, iconSize)); + voiceBtn_->setDefault(true); + cancelBtn_ = new QPushButton(tr("Cancel"), this); + + buttonLayout->addWidget(avatar); + buttonLayout->addStretch(); + buttonLayout->addWidget(voiceBtn_); + buttonLayout->addWidget(cancelBtn_); + + QString name = displayName.isEmpty() ? callee : displayName; + QLabel *label = new QLabel("Place a call to " + name + "?", this); + + auto deviceLayout = new QHBoxLayout; + auto audioLabel = new QLabel(this); + audioLabel->setPixmap(QIcon(":/icons/icons/ui/microphone-unmute.png") + .pixmap(QSize(iconSize * 1.2, iconSize * 1.2))); + + auto deviceList = new QComboBox(this); + for (const auto &d : audioDevices_) + deviceList->addItem(QString::fromStdString(d)); + + deviceLayout->addStretch(); + deviceLayout->addWidget(audioLabel); + deviceLayout->addWidget(deviceList); + + layout->addWidget(label); + layout->addLayout(buttonLayout); + layout->addLayout(deviceLayout); + + connect(voiceBtn_, &QPushButton::clicked, this, [this, deviceList, settings]() { + WebRTCSession::instance().setAudioSource(deviceList->currentIndex()); + settings->setDefaultAudioSource( + QString::fromStdString(audioDevices_[deviceList->currentIndex()])); + emit voice(); + emit close(); + }); + connect(cancelBtn_, &QPushButton::clicked, this, [this]() { + emit cancel(); + emit close(); + }); +} +} diff --git a/src/dialogs/PlaceCall.h b/src/dialogs/PlaceCall.h new file mode 100644
index 00000000..e178afc4 --- /dev/null +++ b/src/dialogs/PlaceCall.h
@@ -0,0 +1,36 @@ +#pragma once + +#include <string> +#include <vector> + +#include <QSharedPointer> +#include <QWidget> + +class QPushButton; +class QString; +class UserSettings; + +namespace dialogs { + +class PlaceCall : public QWidget +{ + Q_OBJECT + +public: + PlaceCall(const QString &callee, + const QString &displayName, + const QString &roomName, + const QString &avatarUrl, + QSharedPointer<UserSettings> settings, + QWidget *parent = nullptr); + +signals: + void voice(); + void cancel(); + +private: + QPushButton *voiceBtn_; + QPushButton *cancelBtn_; + std::vector<std::string> audioDevices_; +}; +} diff --git a/src/dialogs/UserProfile.cpp b/src/dialogs/UserProfile.cpp new file mode 100644
index 00000000..086dbb40 --- /dev/null +++ b/src/dialogs/UserProfile.cpp
@@ -0,0 +1,316 @@ +#include <QHBoxLayout> +#include <QLabel> +#include <QListWidget> +#include <QMessageBox> +#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()}; + + if (QMessageBox::question( + this, + tr("Confirm DM"), + tr("Do you really want to invite %1 (%2) to a direct chat?") + .arg(cache::displayName(roomId_, user_id)) + .arg(user_id)) != QMessageBox::Yes) + return; + + 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(); + + this->roomId_ = roomId; + + auto displayName = cache::displayName(roomId, userId); + + userIdLabel_->setText(userId); + displayNameLabel_->setText(displayName); + avatar_->setLetter(utils::firstChar(displayName)); + + avatar_->setImage(roomId, userId); + + auto localUser = utils::localUser(); + + try { + bool hasMemberRights = + cache::hasEnoughPowerLevel({mtx::events::EventType::RoomMember}, + roomId.toStdString(), + localUser.toStdString()); + if (!hasMemberRights) { + kickBtn_->hide(); + banBtn_->hide(); + } else { + kickBtn_->show(); + banBtn_->show(); + } + } catch (const lmdb::error &e) { + nhlog::db()->warn("lmdb error: {}", e.what()); + } + + if (localUser == userId) { + // TODO: click on display name & avatar to change. + kickBtn_->hide(); + banBtn_->hide(); + ignoreBtn_->hide(); + } + + mtx::requests::QueryKeys req; + req.device_keys[userId.toStdString()] = {}; + + // A proxy object is used to emit the signal instead of the original object + // which might be destroyed by the time the http call finishes. + auto proxy = std::make_shared<Proxy>(); + QObject::connect(proxy.get(), &Proxy::done, this, &UserProfile::updateDeviceList); + + http::client()->query_keys( + req, + [user_id = userId.toStdString(), proxy = std::move(proxy)]( + const mtx::responses::QueryKeys &res, mtx::http::RequestErr err) { + if (err) { + nhlog::net()->warn("failed to query device keys: {} {}", + err->matrix_error.error, + static_cast<int>(err->status_code)); + // TODO: Notify the UI. + return; + } + + if (res.device_keys.empty() || + (res.device_keys.find(user_id) == res.device_keys.end())) { + nhlog::net()->warn("no devices retrieved {}", user_id); + return; + } + + auto devices = res.device_keys.at(user_id); + + std::vector<DeviceInfo> deviceInfo; + for (const auto &d : devices) { + auto device = d.second; + + // TODO: Verify signatures and ignore those that don't pass. + deviceInfo.emplace_back(DeviceInfo{ + QString::fromStdString(d.first), + QString::fromStdString(device.unsigned_info.device_display_name)}); + } + + std::sort(deviceInfo.begin(), + deviceInfo.end(), + [](const DeviceInfo &a, const DeviceInfo &b) { + return a.device_id > b.device_id; + }); + + if (!deviceInfo.empty()) + emit proxy->done(QString::fromStdString(user_id), deviceInfo); + }); +} + +void +UserProfile::updateDeviceList(const QString &user_id, const std::vector<DeviceInfo> &devices) +{ + if (user_id != userIdLabel_->text()) + return; + + for (const auto &dev : devices) { + auto deviceItem = new DeviceItem(dev, this); + auto item = new QListWidgetItem; + + item->setSizeHint(deviceItem->minimumSizeHint()); + item->setFlags(Qt::NoItemFlags); + item->setTextAlignment(Qt::AlignCenter); + + devices_->insertItem(devices_->count() - 1, item); + devices_->setItemWidget(item, deviceItem); + } + + devicesLabel_->show(); + devices_->show(); + adjustSize(); +} diff --git a/src/dialogs/UserProfile.h b/src/dialogs/UserProfile.h new file mode 100644
index 00000000..8129fdcf --- /dev/null +++ b/src/dialogs/UserProfile.h
@@ -0,0 +1,70 @@ +#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_; + QString roomId_; + + QLabel *userIdLabel_; + QLabel *displayNameLabel_; + + FlatButton *banBtn_; + FlatButton *kickBtn_; + FlatButton *ignoreBtn_; + FlatButton *startChat_; + + QLabel *devicesLabel_; + QListWidget *devices_; +}; + +} // dialogs