Add the ability to change the room avatar

fixes #418
This commit is contained in:
Konstantinos Sideris 2018-08-29 16:00:07 +03:00
parent db9c37d336
commit 56ee290b03
3 changed files with 181 additions and 10 deletions

View File

@ -1,7 +1,10 @@
#include <QApplication>
#include <QComboBox>
#include <QFileDialog>
#include <QImageReader>
#include <QLabel>
#include <QMessageBox>
#include <QMimeDatabase>
#include <QPainter>
#include <QPixmap>
#include <QShowEvent>
@ -356,6 +359,103 @@ RoomSettings::RoomSettings(const QString &room_id, QWidget *parent)
else
avatar_->setImage(avatarImg_);
if (canChangeAvatar(room_id_.toStdString(), utils::localUser().toStdString())) {
auto filter = new ClickableFilter(this);
avatar_->installEventFilter(filter);
avatar_->setCursor(Qt::PointingHandCursor);
connect(filter, &ClickableFilter::clicked, this, [this]() {
const auto fileName = QFileDialog::getOpenFileName(
this, tr("Select an avatar"), "", tr("All Files (*)"));
if (fileName.isEmpty())
return;
QMimeDatabase db;
QMimeType mime = db.mimeTypeForFile(fileName, QMimeDatabase::MatchContent);
const auto format = mime.name().split("/")[0];
QFile file{fileName, this};
if (format != "image") {
displayErrorMessage(tr("The selected media is not an image"));
return;
}
if (!file.open(QIODevice::ReadOnly)) {
displayErrorMessage(
tr("Error while reading media: %1").arg(file.errorString()));
return;
}
if (spinner_) {
startLoadingSpinner();
resetErrorLabel();
}
// Events emitted from the http callbacks (different threads) will
// be queued back into the UI thread through this proxy object.
auto proxy = std::make_shared<ThreadProxy>();
connect(proxy.get(),
&ThreadProxy::error,
this,
&RoomSettings::displayErrorMessage);
connect(
proxy.get(), &ThreadProxy::avatarChanged, this, &RoomSettings::setAvatar);
const auto bin = file.peek(file.size());
const auto payload = std::string(bin.data(), bin.size());
const auto dimensions = QImageReader(&file).size();
// First we need to create a new mxc URI
// (i.e upload media to the Matrix content repository) for the new avatar.
http::client()->upload(
payload,
mime.name().toStdString(),
QFileInfo(fileName).fileName().toStdString(),
[proxy = std::move(proxy),
dimensions,
payload,
mimetype = mime.name().toStdString(),
size = payload.size(),
room_id = room_id_.toStdString(),
content = std::move(bin)](const mtx::responses::ContentURI &res,
mtx::http::RequestErr err) {
if (err) {
emit proxy->error(tr("Failed to upload image: %s")
.arg(QString::fromStdString(
err->matrix_error.error)));
return;
}
using namespace mtx::events;
state::Avatar avatar_event;
avatar_event.image_info.w = dimensions.width();
avatar_event.image_info.h = dimensions.height();
avatar_event.image_info.mimetype = mimetype;
avatar_event.image_info.size = size;
avatar_event.url = res.content_uri;
http::client()
->send_state_event<state::Avatar, EventType::RoomAvatar>(
room_id,
avatar_event,
[content = std::move(content),
proxy = std::move(proxy)](const mtx::responses::EventId &,
mtx::http::RequestErr err) {
if (err) {
emit proxy->error(
tr("Failed to upload image: %s")
.arg(QString::fromStdString(
err->matrix_error.error)));
return;
}
emit proxy->avatarChanged(QImage::fromData(content));
});
});
});
}
auto roomNameLabel = new QLabel(QString::fromStdString(info_.name), this);
roomNameLabel->setFont(doubleFont);
@ -537,6 +637,19 @@ RoomSettings::canChangeNameAndTopic(const std::string &room_id, const std::strin
return false;
}
bool
RoomSettings::canChangeAvatar(const std::string &room_id, const std::string &user_id) const
{
try {
return cache::client()->hasEnoughPowerLevel(
{EventType::RoomAvatar}, room_id, user_id);
} catch (const lmdb::error &e) {
nhlog::db()->warn("lmdb error: {}", e.what());
}
return false;
}
void
RoomSettings::updateAccessRules(const std::string &room_id,
const mtx::events::state::JoinRules &join_rule,
@ -596,6 +709,26 @@ RoomSettings::startLoadingSpinner()
}
}
void
RoomSettings::displayErrorMessage(const QString &msg)
{
stopLoadingSpinner();
errorLabel_->show();
errorLabel_->setText(msg);
}
void
RoomSettings::setAvatar(const QImage &img)
{
stopLoadingSpinner();
avatarImg_ = img;
if (avatar_)
avatar_->setImage(img);
}
void
RoomSettings::resetErrorLabel()
{

View File

@ -1,5 +1,6 @@
#pragma once
#include <QEvent>
#include <QFrame>
#include <QImage>
@ -19,6 +20,41 @@ class TextField;
class TextField;
class Toggle;
class ClickableFilter : public QObject
{
Q_OBJECT
public:
explicit ClickableFilter(QWidget *parent)
: QObject(parent)
{}
signals:
void clicked();
protected:
bool eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::MouseButtonRelease) {
emit clicked();
return true;
}
return QObject::eventFilter(obj, event);
}
};
/// Convenience class which connects events emmited from threads
/// outside of main with the UI code.
class ThreadProxy : public QObject
{
Q_OBJECT
signals:
void error(const QString &msg);
void avatarChanged(const QImage &img);
};
class EditModal : public QWidget
{
Q_OBJECT
@ -71,36 +107,39 @@ private:
bool canChangeJoinRules(const std::string &room_id, const std::string &user_id) const;
//! Whether the user has enough power level to send m.room.name & m.room.topic events.
bool canChangeNameAndTopic(const std::string &room_id, const std::string &user_id) const;
//! Whether the user has enough power level to send m.room.avatar event.
bool canChangeAvatar(const std::string &room_id, const std::string &user_id) const;
void updateAccessRules(const std::string &room_id,
const mtx::events::state::JoinRules &,
const mtx::events::state::GuestAccess &);
void stopLoadingSpinner();
void startLoadingSpinner();
void resetErrorLabel();
void displayErrorMessage(const QString &msg);
void setAvatar(const QImage &img) { avatarImg_ = img; }
void setAvatar(const QImage &img);
void setupEditButton();
//! Retrieve the current room information from cache.
void retrieveRoomInfo();
void enableEncryption();
Avatar *avatar_;
Avatar *avatar_ = nullptr;
bool usesEncryption_ = false;
QHBoxLayout *btnLayout_;
FlatButton *editFieldsBtn_;
FlatButton *editFieldsBtn_ = nullptr;
RoomInfo info_;
QString room_id_;
QImage avatarImg_;
QLabel *errorLabel_;
LoadingIndicator *spinner_;
QLabel *errorLabel_ = nullptr;
LoadingIndicator *spinner_ = nullptr;
QComboBox *accessCombo;
Toggle *encryptionToggle_;
Toggle *keyRequestsToggle_;
QComboBox *accessCombo = nullptr;
Toggle *encryptionToggle_ = nullptr;
Toggle *keyRequestsToggle_ = nullptr;
};
} // dialogs

View File

@ -639,18 +639,17 @@ TimelineItem::generateUserName(const QString &user_id, const QString &displaynam
auto filter = new UserProfileFilter(user_id, userName_);
userName_->installEventFilter(filter);
userName_->setCursor(Qt::PointingHandCursor);
connect(filter, &UserProfileFilter::hoverOn, this, [this]() {
QFont f = userName_->font();
f.setUnderline(true);
userName_->setCursor(Qt::PointingHandCursor);
userName_->setFont(f);
});
connect(filter, &UserProfileFilter::hoverOff, this, [this]() {
QFont f = userName_->font();
f.setUnderline(false);
userName_->setCursor(Qt::ArrowCursor);
userName_->setFont(f);
});