summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/dialogs/RoomSettings.cpp133
-rw-r--r--src/dialogs/RoomSettings.h55
-rw-r--r--src/timeline/TimelineItem.cpp3
3 files changed, 181 insertions, 10 deletions
diff --git a/src/dialogs/RoomSettings.cpp b/src/dialogs/RoomSettings.cpp
index 2cba2990..ff3c92a3 100644
--- a/src/dialogs/RoomSettings.cpp
+++ b/src/dialogs/RoomSettings.cpp
@@ -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,
@@ -597,6 +710,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()
 {
         if (errorLabel_) {
diff --git a/src/dialogs/RoomSettings.h b/src/dialogs/RoomSettings.h
index 408804ba..ebc9f04a 100644
--- a/src/dialogs/RoomSettings.h
+++ b/src/dialogs/RoomSettings.h
@@ -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
diff --git a/src/timeline/TimelineItem.cpp b/src/timeline/TimelineItem.cpp
index f50f9e27..8726ec59 100644
--- a/src/timeline/TimelineItem.cpp
+++ b/src/timeline/TimelineItem.cpp
@@ -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);
         });