Merge pull request #445 from Jedi18/avatar_username_feature

Change user avatar (global and room avatar)
This commit is contained in:
DeepBlueV7.X 2021-02-03 03:26:44 +01:00 committed by GitHub
commit e0207ff337
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 207 additions and 26 deletions

View file

@ -101,6 +101,7 @@ ListView {
spacing: 8 spacing: 8
Avatar { Avatar {
id: messageUserAvatar
width: avatarSize width: avatarSize
height: avatarSize height: avatarSize
url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : "" url: modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
@ -109,6 +110,13 @@ ListView {
onClicked: chat.model.openUserProfile(modelData.userId) onClicked: chat.model.openUserProfile(modelData.userId)
} }
Connections {
target: chat.model
onRoomAvatarUrlChanged: {
messageUserAvatar.url = modelData ? chat.model.avatarUrl(modelData.userId).replace("mxc://", "image://MxcImage/") : ""
}
}
Label { Label {
id: userName id: userName

View file

@ -38,7 +38,45 @@ ApplicationWindow {
displayName: profile.displayName displayName: profile.displayName
userid: profile.userid userid: profile.userid
Layout.alignment: Qt.AlignHCenter 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 { TextInput {

View file

@ -801,7 +801,10 @@ TimelineModel::viewDecryptedRawMessage(QString id) const
void void
TimelineModel::openUserProfile(QString userid, bool global) 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 void

View file

@ -1,14 +1,17 @@
#include "UserProfile.h" #include <QFileDialog>
#include <QImageReader>
#include <QMimeDatabase>
#include <QStandardPaths>
#include "Cache_p.h" #include "Cache_p.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "DeviceVerificationFlow.h" #include "DeviceVerificationFlow.h"
#include "Logging.h" #include "Logging.h"
#include "UserProfile.h"
#include "Utils.h" #include "Utils.h"
#include "mtx/responses/crypto.hpp" #include "mtx/responses/crypto.hpp"
#include "timeline/TimelineModel.h" #include "timeline/TimelineModel.h"
#include "timeline/TimelineViewManager.h" #include "timeline/TimelineViewManager.h"
#include <mtx/responses.hpp>
#include <mtx/responses/common.hpp>
UserProfile::UserProfile(QString roomid, UserProfile::UserProfile(QString roomid,
QString userid, QString userid,
@ -21,6 +24,7 @@ UserProfile::UserProfile(QString roomid,
, model(parent) , model(parent)
{ {
fetchDeviceList(this->userid_); fetchDeviceList(this->userid_);
globalAvatarUrl = "";
connect(cache::client(), connect(cache::client(),
&Cache::verificationStatusChanged, &Cache::verificationStatusChanged,
@ -53,16 +57,9 @@ UserProfile::UserProfile(QString roomid,
&UserProfile::setGlobalUsername, &UserProfile::setGlobalUsername,
Qt::QueuedConnection); Qt::QueuedConnection);
http::client()->get_profile( if (isGlobalUserProfile()) {
userid_.toStdString(), getGlobalProfileData();
[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));
});
} }
QHash<int, QByteArray> QHash<int, QByteArray>
@ -122,7 +119,10 @@ UserProfile::displayName()
QString QString
UserProfile::avatarUrl() UserProfile::avatarUrl()
{ {
return cache::avatarUrl(roomid_, userid_); return (isGlobalUserProfile() && globalAvatarUrl != "")
? globalAvatarUrl
: cache::avatarUrl(roomid_, userid_);
;
} }
bool bool
@ -260,15 +260,7 @@ UserProfile::changeUsername(QString username)
.toStdString(); .toStdString();
member.membership = mtx::events::state::Membership::Join; member.membership = mtx::events::state::Membership::Join;
http::client()->send_state_event( updateRoomMemberState(std::move(member));
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);
});
} }
} }
@ -294,3 +286,126 @@ UserProfile::setGlobalUsername(const QString &globalUser)
globalUsername = globalUser; globalUsername = globalUser;
emit displayNameChanged(); 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();
});
}

View file

@ -4,6 +4,8 @@
#include <QObject> #include <QObject>
#include <QString> #include <QString>
#include <QVector> #include <QVector>
#include <mtx/responses.hpp>
#include <mtx/responses/common.hpp>
namespace verification { namespace verification {
Q_NAMESPACE Q_NAMESPACE
@ -81,10 +83,11 @@ class UserProfile : public QObject
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged) Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameChanged)
Q_PROPERTY(QString userid READ userid CONSTANT) 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(DeviceInfoModel *deviceList READ deviceList CONSTANT)
Q_PROPERTY(bool isGlobalUserProfile READ isGlobalUserProfile CONSTANT) Q_PROPERTY(bool isGlobalUserProfile READ isGlobalUserProfile CONSTANT)
Q_PROPERTY(bool isUserVerified READ getUserStatus NOTIFY userStatusChanged) Q_PROPERTY(bool isUserVerified READ getUserStatus NOTIFY userStatusChanged)
Q_PROPERTY(bool isLoading READ isLoading NOTIFY loadingChanged)
Q_PROPERTY( Q_PROPERTY(
bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged) bool userVerificationEnabled READ userVerificationEnabled NOTIFY userStatusChanged)
Q_PROPERTY(bool isSelf READ isSelf CONSTANT) Q_PROPERTY(bool isSelf READ isSelf CONSTANT)
@ -103,6 +106,7 @@ public:
bool getUserStatus(); bool getUserStatus();
bool userVerificationEnabled() const; bool userVerificationEnabled() const;
bool isSelf() const; bool isSelf() const;
bool isLoading() const;
Q_INVOKABLE void verify(QString device = ""); Q_INVOKABLE void verify(QString device = "");
Q_INVOKABLE void unverify(QString device = ""); Q_INVOKABLE void unverify(QString device = "");
@ -112,21 +116,34 @@ public:
Q_INVOKABLE void kickUser(); Q_INVOKABLE void kickUser();
Q_INVOKABLE void startChat(); Q_INVOKABLE void startChat();
Q_INVOKABLE void changeUsername(QString username); Q_INVOKABLE void changeUsername(QString username);
Q_INVOKABLE void changeAvatar();
signals: signals:
void userStatusChanged(); void userStatusChanged();
void loadingChanged();
void displayNameChanged(); void displayNameChanged();
void avatarUrlChanged();
void displayError(const QString &errorMessage);
void globalUsernameRetrieved(const QString &globalUser); void globalUsernameRetrieved(const QString &globalUser);
public slots:
void updateAvatarUrl();
protected slots: protected slots:
void setGlobalUsername(const QString &globalUser); void setGlobalUsername(const QString &globalUser);
private:
void updateRoomMemberState(mtx::events::state::Member member);
void getGlobalProfileData();
private: private:
QString roomid_, userid_; QString roomid_, userid_;
QString globalUsername; QString globalUsername;
QString globalAvatarUrl;
DeviceInfoModel deviceList_; DeviceInfoModel deviceList_;
bool isUserVerified = false; bool isUserVerified = false;
bool hasMasterKey = false; bool hasMasterKey = false;
bool isLoading_ = false;
TimelineViewManager *manager; TimelineViewManager *manager;
TimelineModel *model; TimelineModel *model;
}; };