summary refs log tree commit diff
diff options
context:
space:
mode:
authorDeepBlueV7.X <nicolas.werner@hotmail.de>2021-02-03 03:26:44 +0100
committerGitHub <noreply@github.com>2021-02-03 03:26:44 +0100
commite0207ff337b954e72c90b73e43527baa2c0b33a4 (patch)
treefb8ecf83dadec49096bb06c01684a53ea980d17e
parentCleanup privacy screen, no more grabImage (diff)
parentMerge branch 'master' into avatar_username_feature (diff)
downloadnheko-e0207ff337b954e72c90b73e43527baa2c0b33a4.tar.xz
Merge pull request #445 from Jedi18/avatar_username_feature
Change user avatar (global and room avatar)
Diffstat (limited to '')
-rw-r--r--resources/qml/MessageView.qml8
-rw-r--r--resources/qml/UserProfile.qml40
-rw-r--r--src/timeline/TimelineModel.cpp5
-rw-r--r--src/ui/UserProfile.cpp161
-rw-r--r--src/ui/UserProfile.h19
5 files changed, 207 insertions, 26 deletions
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index dae3e5d7..29115b00 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -101,6 +101,7 @@ ListView {
                 spacing: 8
 
                 Avatar {
+                    id: messageUserAvatar
                     width: avatarSize
                     height: avatarSize
                     url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
@@ -109,6 +110,13 @@ ListView {
                     onClicked: chat.model.openUserProfile(modelData.userId)
                 }
 
+                Connections {
+                    target: chat.model
+                    onRoomAvatarUrlChanged: { 
+                      messageUserAvatar.url = modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
+                    }
+                }
+
                 Label {
                     id: userName
 
diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml
index 4a402b69..37ae6de8 100644
--- a/resources/qml/UserProfile.qml
+++ b/resources/qml/UserProfile.qml
@@ -38,7 +38,45 @@ ApplicationWindow {
             displayName: profile.displayName
             userid: profile.userid
             Layout.alignment: Qt.AlignHCenter
-            onClicked: TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id)
+            onClicked: profile.isSelf ? profile.changeAvatar() : TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id)
+        }
+
+        BusyIndicator {
+            Layout.alignment: Qt.AlignHCenter
+            running: profile.isLoading
+            visible: profile.isLoading
+        }
+
+        Text {
+            id: errorText
+            text: "Error Text"
+            color: "red"
+            visible: opacity > 0
+            opacity: 0
+            Layout.alignment: Qt.AlignHCenter
+        }
+
+        SequentialAnimation {
+            id: hideErrorAnimation
+            running: false
+            PauseAnimation {
+                duration: 4000
+            }
+            NumberAnimation {
+                target: errorText
+                property: 'opacity'
+                to: 0
+                duration: 1000
+            }
+        }
+
+        Connections{
+            target: profile
+            onDisplayError: {
+                errorText.text = errorMessage
+                errorText.opacity = 1
+                hideErrorAnimation.restart()
+            }
         }
 
         TextInput {
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 79cf5184..968ec3c7 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -801,7 +801,10 @@ TimelineModel::viewDecryptedRawMessage(QString id) const
 void
 TimelineModel::openUserProfile(QString userid, bool global)
 {
-        emit openProfile(new UserProfile(global ? "" : room_id_, userid, manager_, this));
+        UserProfile *userProfile = new UserProfile(global ? "" : room_id_, userid, manager_, this);
+        connect(
+          this, &TimelineModel::roomAvatarUrlChanged, userProfile, &UserProfile::updateAvatarUrl);
+        emit openProfile(userProfile);
 }
 
 void
diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp
index de02bf5e..274ed927 100644
--- a/src/ui/UserProfile.cpp
+++ b/src/ui/UserProfile.cpp
@@ -1,14 +1,17 @@
-#include "UserProfile.h"
+#include <QFileDialog>
+#include <QImageReader>
+#include <QMimeDatabase>
+#include <QStandardPaths>
+
 #include "Cache_p.h"
 #include "ChatPage.h"
 #include "DeviceVerificationFlow.h"
 #include "Logging.h"
+#include "UserProfile.h"
 #include "Utils.h"
 #include "mtx/responses/crypto.hpp"
 #include "timeline/TimelineModel.h"
 #include "timeline/TimelineViewManager.h"
-#include <mtx/responses.hpp>
-#include <mtx/responses/common.hpp>
 
 UserProfile::UserProfile(QString roomid,
                          QString userid,
@@ -21,6 +24,7 @@ UserProfile::UserProfile(QString roomid,
   , model(parent)
 {
         fetchDeviceList(this->userid_);
+        globalAvatarUrl = "";
 
         connect(cache::client(),
                 &Cache::verificationStatusChanged,
@@ -53,16 +57,9 @@ UserProfile::UserProfile(QString roomid,
                 &UserProfile::setGlobalUsername,
                 Qt::QueuedConnection);
 
-        http::client()->get_profile(
-          userid_.toStdString(),
-          [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
-                  if (err) {
-                          nhlog::net()->warn("failed to retrieve own profile info");
-                          return;
-                  }
-
-                  emit globalUsernameRetrieved(QString::fromStdString(res.display_name));
-          });
+        if (isGlobalUserProfile()) {
+                getGlobalProfileData();
+        }
 }
 
 QHash<int, QByteArray>
@@ -122,7 +119,10 @@ UserProfile::displayName()
 QString
 UserProfile::avatarUrl()
 {
-        return cache::avatarUrl(roomid_, userid_);
+        return (isGlobalUserProfile() && globalAvatarUrl != "")
+                 ? globalAvatarUrl
+                 : cache::avatarUrl(roomid_, userid_);
+        ;
 }
 
 bool
@@ -260,15 +260,7 @@ UserProfile::changeUsername(QString username)
                     .toStdString();
                 member.membership = mtx::events::state::Membership::Join;
 
-                http::client()->send_state_event(
-                  roomid_.toStdString(),
-                  http::client()->user_id().to_string(),
-                  member,
-                  [](mtx::responses::EventId, mtx::http::RequestErr err) {
-                          if (err)
-                                  nhlog::net()->error("Failed to set room displayname: {}",
-                                                      err->matrix_error.error);
-                  });
+                updateRoomMemberState(std::move(member));
         }
 }
 
@@ -294,3 +286,126 @@ UserProfile::setGlobalUsername(const QString &globalUser)
         globalUsername = globalUser;
         emit displayNameChanged();
 }
+
+void
+UserProfile::changeAvatar()
+{
+        const QString picturesFolder =
+          QStandardPaths::writableLocation(QStandardPaths::PicturesLocation);
+        const QString fileName = QFileDialog::getOpenFileName(
+          nullptr, tr("Select an avatar"), picturesFolder, 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") {
+                emit displayError(tr("The selected file is not an image"));
+                return;
+        }
+
+        if (!file.open(QIODevice::ReadOnly)) {
+                emit displayError(tr("Error while reading file: %1").arg(file.errorString()));
+                return;
+        }
+
+        const auto bin        = file.peek(file.size());
+        const auto payload    = std::string(bin.data(), bin.size());
+        const auto dimensions = QImageReader(&file).size();
+
+        isLoading_ = true;
+        emit loadingChanged();
+
+        // 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(),
+          [this,
+           dimensions,
+           payload,
+           mimetype = mime.name().toStdString(),
+           size     = payload.size(),
+           room_id  = roomid_.toStdString(),
+           content  = std::move(bin)](const mtx::responses::ContentURI &res,
+                                     mtx::http::RequestErr err) {
+                  if (err) {
+                          nhlog::ui()->error("Failed to upload image", err->matrix_error.error);
+                          return;
+                  }
+
+                  if (isGlobalUserProfile()) {
+                          http::client()->set_avatar_url(
+                            res.content_uri, [this](mtx::http::RequestErr err) {
+                                    if (err) {
+                                            nhlog::ui()->error("Failed to set user avatar url",
+                                                               err->matrix_error.error);
+                                    }
+
+                                    isLoading_ = false;
+                                    emit loadingChanged();
+                                    getGlobalProfileData();
+                            });
+                  } else {
+                          // change room username
+                          mtx::events::state::Member member;
+                          member.display_name = cache::displayName(roomid_, userid_).toStdString();
+                          member.avatar_url   = res.content_uri;
+                          member.membership   = mtx::events::state::Membership::Join;
+
+                          updateRoomMemberState(std::move(member));
+                  }
+          });
+}
+
+void
+UserProfile::updateRoomMemberState(mtx::events::state::Member member)
+{
+        http::client()->send_state_event(
+          roomid_.toStdString(),
+          http::client()->user_id().to_string(),
+          member,
+          [this](mtx::responses::EventId, mtx::http::RequestErr err) {
+                  if (err)
+                          nhlog::net()->error("Failed to update room member state : ",
+                                              err->matrix_error.error);
+          });
+}
+
+void
+UserProfile::updateAvatarUrl()
+{
+        isLoading_ = false;
+        emit loadingChanged();
+
+        emit avatarUrlChanged();
+}
+
+bool
+UserProfile::isLoading() const
+{
+        return isLoading_;
+}
+
+void
+UserProfile::getGlobalProfileData()
+{
+        http::client()->get_profile(
+          userid_.toStdString(),
+          [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
+                  if (err) {
+                          nhlog::net()->warn("failed to retrieve own profile info");
+                          return;
+                  }
+
+                  emit globalUsernameRetrieved(QString::fromStdString(res.display_name));
+                  globalAvatarUrl = QString::fromStdString(res.avatar_url);
+                  emit avatarUrlChanged();
+          });
+}
diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h
index 11f588b6..ffc5dcae 100644
--- a/src/ui/UserProfile.h
+++ b/src/ui/UserProfile.h
@@ -4,6 +4,8 @@
 #include <QObject>
 #include <QString>
 #include <QVector>
+#include <mtx/responses.hpp>
+#include <mtx/responses/common.hpp>
 
 namespace verification {
 Q_NAMESPACE
@@ -81,10 +83,11 @@ class UserProfile : public QObject
         Q_OBJECT
         Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged)
         Q_PROPERTY(QString userid READ userid CONSTANT)
-        Q_PROPERTY(QString avatarUrl READ avatarUrl CONSTANT)
+        Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
         Q_PROPERTY(DeviceInfoModel *deviceList READ deviceList CONSTANT)
         Q_PROPERTY(bool isGlobalUserProfile READ isGlobalUserProfile CONSTANT)
         Q_PROPERTY(bool isUserVerified READ getUserStatus NOTIFY userStatusChanged)
+        Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
         Q_PROPERTY(
           bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged)
         Q_PROPERTY(bool isSelf READ isSelf CONSTANT)
@@ -103,6 +106,7 @@ public:
         bool getUserStatus();
         bool userVerificationEnabled() const;
         bool isSelf() const;
+        bool isLoading() const;
 
         Q_INVOKABLE void verify(QString device = "");
         Q_INVOKABLE void unverify(QString device = "");
@@ -112,21 +116,34 @@ public:
         Q_INVOKABLE void kickUser();
         Q_INVOKABLE void startChat();
         Q_INVOKABLE void changeUsername(QString username);
+        Q_INVOKABLE void changeAvatar();
 
 signals:
         void userStatusChanged();
+        void loadingChanged();
         void displayNameChanged();
+        void avatarUrlChanged();
+        void displayError(const QString &errorMessage);
         void globalUsernameRetrieved(const QString &globalUser);
 
+public slots:
+        void updateAvatarUrl();
+
 protected slots:
         void setGlobalUsername(const QString &globalUser);
 
 private:
+        void updateRoomMemberState(mtx::events::state::Member member);
+        void getGlobalProfileData();
+
+private:
         QString roomid_, userid_;
         QString globalUsername;
+        QString globalAvatarUrl;
         DeviceInfoModel deviceList_;
         bool isUserVerified = false;
         bool hasMasterKey   = false;
+        bool isLoading_     = false;
         TimelineViewManager *manager;
         TimelineModel *model;
 };