From c3e02240bff004cfbde8e747894489dc939b1ebe Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Mon, 1 Feb 2021 22:13:04 +0530 Subject: [PATCH 1/4] update room and global avatar through user profile --- resources/qml/UserProfile.qml | 2 +- src/ui/UserProfile.cpp | 103 ++++++++++++++++++++++++++++++---- src/ui/UserProfile.h | 6 ++ 3 files changed, 98 insertions(+), 13 deletions(-) diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 26dfc48c..cff69a90 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -31,7 +31,7 @@ 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) } TextInput { diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 3872294a..960cfc4d 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -1,14 +1,17 @@ -#include "UserProfile.h" +#include +#include +#include +#include + #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 -#include UserProfile::UserProfile(QString roomid, QString userid, @@ -260,15 +263,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)); } } @@ -293,4 +288,88 @@ 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") { + // displayErrorMessage(tr("The selected file is not an image")); + return; + } + + if (!file.open(QIODevice::ReadOnly)) { + // displayErrorMessage(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(); + + // 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, [](mtx::http::RequestErr err) { + if (err) { + nhlog::ui()->error("Failed to set user avatar url", + err->matrix_error.error); + } + }); + } 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, + [](mtx::responses::EventId, mtx::http::RequestErr err) { + if (err) + nhlog::net()->error( + "Failed to update room member state : ", + err->matrix_error.error); + }); } \ No newline at end of file diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 11f588b6..69c1542c 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -4,6 +4,8 @@ #include #include #include +#include +#include namespace verification { Q_NAMESPACE @@ -112,6 +114,7 @@ public: Q_INVOKABLE void kickUser(); Q_INVOKABLE void startChat(); Q_INVOKABLE void changeUsername(QString username); + Q_INVOKABLE void changeAvatar(); signals: void userStatusChanged(); @@ -121,6 +124,9 @@ signals: protected slots: void setGlobalUsername(const QString &globalUser); +private: + void updateRoomMemberState(mtx::events::state::Member member); + private: QString roomid_, userid_; QString globalUsername; From d535cc5e759757fc71751df7d89131cdf2cd71d2 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Tue, 2 Feb 2021 13:30:47 +0530 Subject: [PATCH 2/4] add error message and update avatars on avatar change in timeline and user profile dialog --- resources/qml/MessageView.qml | 8 ++++++++ resources/qml/UserProfile.qml | 31 +++++++++++++++++++++++++++++++ src/timeline/TimelineModel.cpp | 5 ++++- src/ui/UserProfile.cpp | 4 ++-- src/ui/UserProfile.h | 4 +++- 5 files changed, 48 insertions(+), 4 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 cff69a90..349fb89f 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -34,6 +34,37 @@ ApplicationWindow { onClicked: profile.isSelf ? profile.changeAvatar() : TimelineManager.openImageOverlay(TimelineManager.timeline.avatarUrl(userid), TimelineManager.timeline.data.id) } + 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.opacity = 1 + hideErrorAnimation.restart() + } + } + TextInput { id: displayUsername diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index 79cf5184..a4d551f5 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::avatarUrlChanged); + emit openProfile(userProfile); } void diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index 960cfc4d..e260c924 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -308,12 +308,12 @@ UserProfile::changeAvatar() QFile file{fileName, this}; if (format != "image") { - // displayErrorMessage(tr("The selected file is not an image")); + emit displayError(tr("The selected file is not an image")); return; } if (!file.open(QIODevice::ReadOnly)) { - // displayErrorMessage(tr("Error while reading file: %1").arg(file.errorString())); + emit displayError(tr("Error while reading file: %1").arg(file.errorString())); return; } diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 69c1542c..9f48f935 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -83,7 +83,7 @@ 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) @@ -119,6 +119,8 @@ public: signals: void userStatusChanged(); void displayNameChanged(); + void avatarUrlChanged(); + void displayError(const QString &errorMessage); void globalUsernameRetrieved(const QString &globalUser); protected slots: From cd3f719e43e06749a09a8e9eff20c4f2a0615742 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Tue, 2 Feb 2021 17:24:08 +0530 Subject: [PATCH 3/4] add loading indicator --- resources/qml/UserProfile.qml | 7 ++++++ src/timeline/TimelineModel.cpp | 2 +- src/ui/UserProfile.cpp | 41 +++++++++++++++++++++++++--------- src/ui/UserProfile.h | 7 ++++++ 4 files changed, 46 insertions(+), 11 deletions(-) diff --git a/resources/qml/UserProfile.qml b/resources/qml/UserProfile.qml index 349fb89f..65c58382 100644 --- a/resources/qml/UserProfile.qml +++ b/resources/qml/UserProfile.qml @@ -34,6 +34,12 @@ ApplicationWindow { 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" @@ -60,6 +66,7 @@ ApplicationWindow { Connections{ target: profile onDisplayError: { + errorText.text = errorMessage errorText.opacity = 1 hideErrorAnimation.restart() } diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp index a4d551f5..968ec3c7 100644 --- a/src/timeline/TimelineModel.cpp +++ b/src/timeline/TimelineModel.cpp @@ -803,7 +803,7 @@ TimelineModel::openUserProfile(QString userid, bool global) { UserProfile *userProfile = new UserProfile(global ? "" : room_id_, userid, manager_, this); connect( - this, &TimelineModel::roomAvatarUrlChanged, userProfile, &UserProfile::avatarUrlChanged); + this, &TimelineModel::roomAvatarUrlChanged, userProfile, &UserProfile::updateAvatarUrl); emit openProfile(userProfile); } diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index e260c924..a3715fee 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -321,6 +321,9 @@ UserProfile::changeAvatar() 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( @@ -342,11 +345,14 @@ UserProfile::changeAvatar() if (isGlobalUserProfile()) { http::client()->set_avatar_url( - res.content_uri, [](mtx::http::RequestErr err) { + 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(); }); } else { // change room username @@ -363,13 +369,28 @@ UserProfile::changeAvatar() void UserProfile::updateRoomMemberState(mtx::events::state::Member member) { - 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 update room member state : ", - err->matrix_error.error); - }); + 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_; } \ No newline at end of file diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 9f48f935..58fb0f0b 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -87,6 +87,7 @@ class UserProfile : public QObject 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) @@ -105,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 = ""); @@ -118,11 +120,15 @@ public: 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); @@ -135,6 +141,7 @@ private: DeviceInfoModel deviceList_; bool isUserVerified = false; bool hasMasterKey = false; + bool isLoading_ = false; TimelineViewManager *manager; TimelineModel *model; }; From 2ff3c0c97eda8f1503ee0c274c01566b34bc84f1 Mon Sep 17 00:00:00 2001 From: Jedi18 Date: Tue, 2 Feb 2021 17:46:02 +0530 Subject: [PATCH 4/4] fixed global avatar updation in the dialog --- src/ui/UserProfile.cpp | 37 ++++++++++++++++++++++++++----------- src/ui/UserProfile.h | 2 ++ 2 files changed, 28 insertions(+), 11 deletions(-) diff --git a/src/ui/UserProfile.cpp b/src/ui/UserProfile.cpp index a3715fee..715c1c42 100644 --- a/src/ui/UserProfile.cpp +++ b/src/ui/UserProfile.cpp @@ -24,6 +24,7 @@ UserProfile::UserProfile(QString roomid, , model(parent) { fetchDeviceList(this->userid_); + globalAvatarUrl = ""; connect(cache::client(), &Cache::verificationStatusChanged, @@ -56,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 @@ -125,7 +119,10 @@ UserProfile::displayName() QString UserProfile::avatarUrl() { - return cache::avatarUrl(roomid_, userid_); + return (isGlobalUserProfile() && globalAvatarUrl != "") + ? globalAvatarUrl + : cache::avatarUrl(roomid_, userid_); + ; } bool @@ -353,6 +350,7 @@ UserProfile::changeAvatar() isLoading_ = false; emit loadingChanged(); + getGlobalProfileData(); }); } else { // change room username @@ -393,4 +391,21 @@ 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(); + }); } \ No newline at end of file diff --git a/src/ui/UserProfile.h b/src/ui/UserProfile.h index 58fb0f0b..ffc5dcae 100644 --- a/src/ui/UserProfile.h +++ b/src/ui/UserProfile.h @@ -134,10 +134,12 @@ protected slots: 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;