mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-10-30 09:30:47 +03:00
Merge pull request #445 from Jedi18/avatar_username_feature
Change user avatar (global and room avatar)
This commit is contained in:
commit
e0207ff337
5 changed files with 207 additions and 26 deletions
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
|
@ -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;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue