Make use of new RoomMember item from libquotient

Depends on https://github.com/quotient-im/libQuotient/pull/695

Currently basic just to show a working implementation using RoomMember. Currently only the room event and search models are moved over. Will change everything else over once the dependent pr is complete.
This commit is contained in:
James Graham
2024-05-05 17:03:28 +00:00
committed by Tobias Fella
parent 58d727350d
commit 430bafafe7
52 changed files with 332 additions and 603 deletions

View File

@@ -6,6 +6,7 @@
#include <QObject>
#include <QTest>
#include <Quotient/roommember.h>
#include <Quotient/syncdata.h>
#include <qtestcase.h>
@@ -50,7 +51,7 @@ void ChatBarCacheTest::empty()
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
@@ -64,7 +65,7 @@ void ChatBarCacheTest::noRoom()
// ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString());
@@ -80,7 +81,7 @@ void ChatBarCacheTest::badParent()
// ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString());
@@ -98,7 +99,7 @@ void ChatBarCacheTest::reply()
QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org")));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
@@ -115,7 +116,7 @@ void ChatBarCacheTest::edit()
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), true);
QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org")));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
@@ -132,7 +133,7 @@ void ChatBarCacheTest::attachment()
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
}

View File

@@ -35,7 +35,7 @@
"content": {
"$153456789:example.org": {
"m.read": {
"@alice:matrix.org": {
"@alice:example.org": {
"ts": 1436451550453
}
}

View File

@@ -37,16 +37,14 @@
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
"displayname": "Example",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234

View File

@@ -51,6 +51,21 @@
"unsigned": {
"age": 1234
}
},
{
"content": {
"displayname": "Example",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},

View File

@@ -101,28 +101,27 @@ void EventHandlerTest::nullEventId()
void EventHandlerTest::author()
{
auto event = room->messageEvents().at(0).get();
auto author = room->user(event->senderId());
auto author = room->member(event->senderId());
EventHandler eventHandler(room, event);
auto eventHandlerAuthor = eventHandler.getAuthor();
QCOMPARE(eventHandlerAuthor["isLocalUser"_ls], author->id() == room->localUser()->id());
QCOMPARE(eventHandlerAuthor["id"_ls], author->id());
QCOMPARE(eventHandlerAuthor["displayName"_ls], author->displayname(room));
QCOMPARE(eventHandlerAuthor["avatarSource"_ls], room->avatarForMember(author));
QCOMPARE(eventHandlerAuthor["avatarMediaId"_ls], author->avatarMediaId(room));
QCOMPARE(eventHandlerAuthor["color"_ls], Utils::getUserColor(author->hueF()));
QCOMPARE(eventHandlerAuthor["object"_ls], QVariant::fromValue(author));
QCOMPARE(eventHandlerAuthor.isLocalMember(), author.id() == room->localMember().id());
QCOMPARE(eventHandlerAuthor.id(), author.id());
QCOMPARE(eventHandlerAuthor.displayName(), author.displayName());
QCOMPARE(eventHandlerAuthor.avatarUrl(), author.avatarUrl());
QCOMPARE(eventHandlerAuthor.avatarMediaId(), author.avatarMediaId());
QCOMPARE(eventHandlerAuthor.color(), author.color());
}
void EventHandlerTest::nullAuthor()
{
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getAuthor(), QVariantMap());
QCOMPARE(emptyHandler.getAuthor(), RoomMember());
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user.");
QCOMPARE(noEventHandler.getAuthor(), room->getUser(nullptr));
QCOMPARE(noEventHandler.getAuthor(), RoomMember());
}
void EventHandlerTest::authorDisplayName()
@@ -393,31 +392,30 @@ void EventHandlerTest::nullReplyId()
void EventHandlerTest::replyAuthor()
{
auto replyEvent = room->messageEvents().at(0).get();
auto replyAuthor = room->user(replyEvent->senderId());
auto replyAuthor = room->member(replyEvent->senderId());
EventHandler eventHandler(room, room->messageEvents().at(5).get());
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
QCOMPARE(eventHandlerReplyAuthor["isLocalUser"_ls], replyAuthor->id() == room->localUser()->id());
QCOMPARE(eventHandlerReplyAuthor["id"_ls], replyAuthor->id());
QCOMPARE(eventHandlerReplyAuthor["displayName"_ls], replyAuthor->displayname(room));
QCOMPARE(eventHandlerReplyAuthor["avatarSource"_ls], room->avatarForMember(replyAuthor));
QCOMPARE(eventHandlerReplyAuthor["avatarMediaId"_ls], replyAuthor->avatarMediaId(room));
QCOMPARE(eventHandlerReplyAuthor["color"_ls], Utils::getUserColor(replyAuthor->hueF()));
QCOMPARE(eventHandlerReplyAuthor["object"_ls], QVariant::fromValue(replyAuthor));
QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id());
QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id());
QCOMPARE(eventHandlerReplyAuthor.displayName(), replyAuthor.displayName());
QCOMPARE(eventHandlerReplyAuthor.avatarUrl(), replyAuthor.avatarUrl());
QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId());
QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color());
EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), room->getUser(nullptr));
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), RoomMember());
}
void EventHandlerTest::nullReplyAuthor()
{
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReplyAuthor(), QVariantMap());
QCOMPARE(emptyHandler.getReplyAuthor(), RoomMember());
EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user.");
QCOMPARE(noEventHandler.getReplyAuthor(), room->getUser(nullptr));
QCOMPARE(noEventHandler.getReplyAuthor(), RoomMember());
}
void EventHandlerTest::replyBody()
@@ -531,10 +529,10 @@ void EventHandlerTest::readMarkers()
auto readMarkers = eventHandler.getReadMarkers();
QCOMPARE(readMarkers.size(), 1);
QCOMPARE(readMarkers[0].toMap()["id"_ls], QStringLiteral("@alice:matrix.org"));
QCOMPARE(readMarkers[0].id(), QStringLiteral("@alice:example.org"));
QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QString());
QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: @alice:matrix.org"));
QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: Alice Margatroid"));
EventHandler eventHandler2(room, room->messageEvents().at(2).get());
QCOMPARE(eventHandler2.hasReadMarkers(), true);
@@ -554,7 +552,7 @@ void EventHandlerTest::nullReadMarkers()
QCOMPARE(emptyHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReadMarkers(), QVariantList());
QCOMPARE(emptyHandler.getReadMarkers(), QList<Quotient::RoomMember>());
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getNumberExcessReadMarkers(), QString());
@@ -568,7 +566,7 @@ void EventHandlerTest::nullReadMarkers()
QCOMPARE(noEventHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReadMarkers(), QVariantList());
QCOMPARE(noEventHandler.getReadMarkers(), QList<Quotient::RoomMember>());
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getNumberExcessReadMarkers(), QString());

View File

@@ -53,9 +53,7 @@ void ReactionModelTest::basicReaction()
QCOMPARE(model.data(model.index(0), ReactionModel::ReactionRole), QStringLiteral("👍"));
QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole),
QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
auto authorList = QVariantList{room->getUser(room->user(QStringLiteral("@alice:matrix.org")))};
QCOMPARE(model.data(model.index(0), ReactionModel::AuthorsRole), authorList);
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalUser), false);
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalMember), false);
}
void ReactionModelTest::newReaction()

View File

@@ -91,7 +91,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
if (event->senderId() == m_room->localUser()->id() && event->hasTextContent()) {
if (event->senderId() == m_room->localMember().id() && event->hasTextContent()) {
QString originalString;
if (event->content()) {
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;

View File

@@ -3,6 +3,8 @@
#include "chatbarcache.h"
#include <Quotient/roommember.h>
#include "chatdocumenthandler.h"
#include "eventhandler.h"
#include "neochatroom.h"
@@ -84,7 +86,7 @@ void ChatBarCache::setEditId(const QString &editId)
Q_EMIT attachmentPathChanged();
}
QVariantMap ChatBarCache::relationUser() const
Quotient::RoomMember ChatBarCache::relationUser() const
{
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
@@ -96,9 +98,9 @@ QVariantMap ChatBarCache::relationUser() const
return {};
}
if (m_relationId.isEmpty()) {
return room->getUser(nullptr);
return room->member(QString());
}
return room->getUser(room->user((*room->findInTimeline(m_relationId))->senderId()));
return room->member((*room->findInTimeline(m_relationId))->senderId());
}
QString ChatBarCache::relationMessage() const

View File

@@ -10,6 +10,12 @@
class ChatDocumentHandler;
namespace Quotient
{
class RoomMember;
}
/**
* @brief Defines a user mention in the current chat or edit text.
*/
@@ -88,26 +94,13 @@ class ChatBarCache : public QObject
Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged)
/**
* @brief Get the user for the message being replied to.
* @brief Get the RoomMember object for the message being replied to.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
* Returns an empty RoomMember if not replying to a message.
*
* Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
* @sa Quotient::RoomMember
*/
Q_PROPERTY(QVariantMap relationUser READ relationUser NOTIFY relationIdChanged)
Q_PROPERTY(Quotient::RoomMember relationUser READ relationUser NOTIFY relationIdChanged)
/**
* @brief The content of the related message.
@@ -161,7 +154,7 @@ public:
QString editId() const;
void setEditId(const QString &editId);
QVariantMap relationUser() const;
Quotient::RoomMember relationUser() const;
QString relationMessage() const;

View File

@@ -19,6 +19,7 @@
#include <Quotient/events/simplestateevents.h>
#include <Quotient/events/stickerevent.h>
#include <Quotient/quotient_common.h>
#include <Quotient/roommember.h>
#include "eventhandler_logging.h"
#include "events/locationbeaconevent.h"
@@ -60,20 +61,18 @@ MessageComponentType::Type EventHandler::messageComponentType() const
return MessageComponentType::typeForEvent(*m_event);
}
QVariantMap EventHandler::getAuthor(bool isPending) const
Quotient::RoomMember EventHandler::getAuthor(bool isPending) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr.";
return {};
}
// If we have a room we can return an empty user by handing nullptr to m_room->getUser.
if (m_event == nullptr) {
qCWarning(EventHandling) << "getAuthor called with m_event set to nullptr. Returning empty user.";
return m_room->getUser(nullptr);
return {};
}
const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId());
return m_room->getUser(author);
return isPending ? m_room->localMember() : m_room->member(m_event->senderId());
}
QString EventHandler::getAuthorDisplayName(bool isPending) const
@@ -95,8 +94,8 @@ QString EventHandler::getAuthorDisplayName(bool isPending) const
}
return previousDisplayName;
} else {
const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId());
return m_room->htmlSafeMemberName(author->id());
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
return author.htmlSafeDisplayName();
}
}
@@ -111,8 +110,8 @@ QString EventHandler::singleLineAuthorDisplayname(bool isPending) const
return {};
}
const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId());
auto displayName = m_room->safeMemberName(author->id());
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
auto displayName = author.displayName();
displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br>"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br />\n"), QStringLiteral(" "));
@@ -219,7 +218,7 @@ bool EventHandler::isHidden()
}
}
if (m_room->connection()->isIgnored(m_room->user(m_event->senderId()))) {
if (m_room->connection()->isIgnored(m_event->senderId())) {
return true;
}
@@ -317,7 +316,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
},
[this, prettyPrint](const RoomMemberEvent &e) {
// FIXME: Rewind to the name that was at the time of this event
auto subjectName = m_room->htmlSafeMemberName(e.userId());
auto subjectName = m_room->member(e.userId()).htmlSafeDisplayName();
if (e.membership() == Membership::Leave) {
if (e.prevContent() && e.prevContent()->displayName) {
subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped();
@@ -325,7 +324,8 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
}
if (prettyPrint) {
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%2</a>").arg(e.userId(), subjectName);
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
.arg(e.userId(), m_room->member(e.userId()).color().name(), subjectName);
}
// The below code assumes senderName output in AuthorRole
@@ -799,25 +799,21 @@ MessageComponentType::Type EventHandler::replyMessageComponentType() const
return MessageComponentType::typeForEvent(*replyEvent);
}
QVariantMap EventHandler::getReplyAuthor() const
Quotient::RoomMember EventHandler::getReplyAuthor() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr.";
return {};
}
// If we have a room we can return an empty user by handing nullptr to m_room->getUser.
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user.";
return m_room->getUser(nullptr);
return {};
}
auto replyPtr = m_room->getReplyForEvent(*m_event);
if (replyPtr) {
auto replyUser = m_room->user(replyPtr->senderId());
return m_room->getUser(replyUser);
if (auto replyPtr = m_room->getReplyForEvent(*m_event)) {
return m_room->member(replyPtr->senderId());
} else {
return m_room->getUser(nullptr);
return m_room->member(QString());
}
}
@@ -965,11 +961,11 @@ bool EventHandler::hasReadMarkers() const
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
userIds.remove(m_room->localMember().id());
return userIds.size() > 0;
}
QVariantList EventHandler::getReadMarkers(int maxMarkers) const
QList<Quotient::RoomMember> EventHandler::getReadMarkers(int maxMarkers) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReadMarkers called with m_room set to nullptr.";
@@ -981,18 +977,17 @@ QVariantList EventHandler::getReadMarkers(int maxMarkers) const
}
auto userIds_temp = m_room->userIdsAtEvent(m_event->id());
userIds_temp.remove(m_room->localUser()->id());
userIds_temp.remove(m_room->localMember().id());
auto userIds = userIds_temp.values();
if (userIds.count() > maxMarkers) {
userIds = userIds.mid(0, maxMarkers);
}
QVariantList users;
QList<Quotient::RoomMember> users;
users.reserve(userIds.size());
for (const auto &userId : userIds) {
auto user = m_room->user(userId);
users += m_room->getUser(user);
users += m_room->member(userId);
}
return users;
@@ -1010,7 +1005,7 @@ QString EventHandler::getNumberExcessReadMarkers(int maxMarkers) const
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
userIds.remove(m_room->localMember().id());
if (userIds.count() > maxMarkers) {
return QStringLiteral("+ ") + QString::number(userIds.count() - maxMarkers);
@@ -1031,7 +1026,7 @@ QString EventHandler::getReadMarkersString() const
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
userIds.remove(m_room->localMember().id());
/**
* The string ends up in the form
@@ -1039,10 +1034,12 @@ QString EventHandler::getReadMarkersString() const
*/
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
for (const auto &userId : userIds) {
auto user = m_room->user(userId);
auto displayName = user->displayname(m_room);
if (displayName.isEmpty()) {
displayName = userId;
auto member = m_room->member(userId);
QString displayName;
if (member.isEmpty()) {
displayName = i18nc("A member who is not in the room has been requested.", "unknown member");
} else {
displayName = member.displayName();
}
readMarkersString += displayName + i18nc("list separator", ", ");
}

View File

@@ -13,6 +13,11 @@
#include "enums/messagecomponenttype.h"
namespace Quotient
{
class RoomMember;
}
class LinkPreviewer;
class NeoChatRoom;
class ReactionModel;
@@ -51,30 +56,17 @@ public:
/**
* @brief Get the author of the event in context of the room.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* uses the room context and outputs the result as QVariantMap.
*
* An empty QVariantMap will be returned if the EventHandler hasn't had the room
* intialised. An empty user (i.e. a QVariantMap with all the correct keys
* but empty values) will be returned if the room has been set but not an event.
* An empty Quotient::RoomMember will be returned if the EventHandler hasn't had
* the room or event initialised.
*
* @param isPending if the event is pending, i.e. has not been confirmed by
* the server.
*
* @return a QVariantMap for the user with the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
* @return a Quotient::RoomMember object for the user.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
QVariantMap getAuthor(bool isPending = false) const;
Quotient::RoomMember getAuthor(bool isPending = false) const;
/**
* @brief Get the display name of the event author.
@@ -251,27 +243,17 @@ public:
/**
* @brief Get the author of the event replied to in context of the room.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* uses the room context and outputs the result as QVariantMap.
* An empty Quotient::RoomMember will be returned if the EventHandler hasn't had
* the room or event initialised.
*
* An empty QVariantMap will be returned if the EventHandler hasn't had the room
* intialised. An empty user (i.e. a QVariantMap with all the correct keys
* but empty values) will be returned if the room has been set but not an event.
* @param isPending if the event is pending, i.e. has not been confirmed by
* the server.
*
* @return a QVariantMap for the user with the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
* @return a Quotient::RoomMember object for the user.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
QVariantMap getReplyAuthor() const;
Quotient::RoomMember getReplyAuthor() const;
/**
* @brief Output a string for the message content of the event replied to ready
@@ -375,7 +357,7 @@ public:
* the number of users shown plus the excess number will be
* the total number of other user read markers at an event.
*/
QVariantList getReadMarkers(int maxMarkers = 5) const;
QList<Quotient::RoomMember> getReadMarkers(int maxMarkers = 5) const;
/**
* @brief Returns the number of excess user read markers for the event.

View File

@@ -9,6 +9,7 @@
#include "roommanager.h"
#include <Quotient/events/roommemberevent.h>
#include <Quotient/events/roompowerlevelsevent.h>
#include <Quotient/user.h>
#include <KLocalizedString>
@@ -202,11 +203,11 @@ QList<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
return QString();
}
if (room->localUser()->id() == text) {
if (room->localMember().id() == text) {
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18n("You are already in this room."));
return QString();
}
if (room->users().contains(room->user(text))) {
if (room->members().contains(room->member(text))) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
return QString();
}
@@ -359,7 +360,7 @@ QList<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
return QString();
}
room->connection()->addToIgnoredUsers(room->connection()->user(text));
room->connection()->addToIgnoredUsers(text);
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
return QString();
},
@@ -382,7 +383,7 @@ QList<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
return QString();
}
room->connection()->removeFromIgnoredUsers(room->connection()->user(text));
room->connection()->removeFromIgnoredUsers(text);
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
return QString();
},
@@ -431,11 +432,11 @@ QList<ActionsModel::Action> actions{
if (!plEvent) {
return QString();
}
if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to ban users from this room."));
return QString();
}
if (plEvent->powerLevelForUser(room->localUser()->id()) <= plEvent->powerLevelForUser(parts[0])) {
if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) {
Q_EMIT room->showMessage(
NeoChatRoom::Error,
i18nc("You are not allowed to ban <username> from this room.", "You are not allowed to ban %1 from this room.", parts[0]));
@@ -464,7 +465,7 @@ QList<ActionsModel::Action> actions{
if (!plEvent) {
return QString();
}
if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to unban users from this room."));
return QString();
}
@@ -495,7 +496,7 @@ QList<ActionsModel::Action> actions{
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0]));
return QString();
}
if (parts[0] == room->localUser()->id()) {
if (parts[0] == room->localMember().id()) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You cannot kick yourself from the room."));
return QString();
}
@@ -508,11 +509,11 @@ QList<ActionsModel::Action> actions{
return QString();
}
auto kick = plEvent->kick();
if (plEvent->powerLevelForUser(room->localUser()->id()) < kick) {
if (plEvent->powerLevelForUser(room->localMember().id()) < kick) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to kick users from this room."));
return QString();
}
if (plEvent->powerLevelForUser(room->localUser()->id()) <= plEvent->powerLevelForUser(parts[0])) {
if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) {
Q_EMIT room->showMessage(
NeoChatRoom::Error,
i18nc("You are not allowed to kick <username> from this room", "You are not allowed to kick %1 from this room.", parts[0]));

View File

@@ -80,7 +80,7 @@ QVariant LiveLocationsModel::data(const QModelIndex &index, int roleName) const
case AssetRole:
return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
case AuthorRole:
return m_room->getUser(data.senderId);
return QVariant::fromValue(m_room->member(data.senderId));
case IsLiveRole: {
if (!data.beaconInfo["live"_ls].toBool()) {
return false;

View File

@@ -63,7 +63,7 @@ void LocationsModel::addLocation(const RoomMessageEvent *event)
.latitude = latitude,
.longitude = longitude,
.content = event->contentJson(),
.author = m_room->user(event->senderId()),
.member = m_room->member(event->senderId()),
};
endInsertRows();
}
@@ -105,7 +105,7 @@ QVariant LocationsModel::data(const QModelIndex &index, int roleName) const
} else if (roleName == AssetRole) {
return m_locations[row].content["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
} else if (roleName == AuthorRole) {
return m_room->getUser(m_locations[row].author);
return QVariant::fromValue(m_locations[row].member);
}
return {};
}

View File

@@ -11,7 +11,7 @@
#include "neochatroom.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/user.h>
#include <Quotient/roommember.h>
class LocationsModel : public QAbstractListModel
{
@@ -57,7 +57,7 @@ private:
float latitude;
float longitude;
QJsonObject content;
Quotient::User *author;
Quotient::RoomMember member;
};
QList<LocationData> m_locations;
void addLocation(const Quotient::RoomMessageEvent *event);

View File

@@ -194,7 +194,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return eventHandler.getId();
}
if (role == AuthorRole) {
return eventHandler.getAuthor(false);
return QVariant::fromValue(eventHandler.getAuthor(false));
}
if (role == MediaInfoRole) {
return eventHandler.getMediaInfo();
@@ -224,7 +224,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return eventHandler.getReplyId();
}
if (role == ReplyAuthorRole) {
return eventHandler.getReplyAuthor();
return QVariant::fromValue(eventHandler.getReplyAuthor());
}
if (role == ReplyContentModelRole) {
return QVariant::fromValue<MessageContentModel *>(m_replyModel);

View File

@@ -11,7 +11,7 @@
#include <Quotient/events/redactionevent.h>
#include <Quotient/events/roommessageevent.h>
#include <Quotient/events/stickerevent.h>
#include <Quotient/user.h>
#include <Quotient/roommember.h>
#include <QDebug>
#include <QGuiApplication>
@@ -222,7 +222,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
beginResetModel();
endResetModel();
});
qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localUser()->id();
qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localMember().id();
} else {
lastReadEventId.clear();
}
@@ -460,7 +460,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == AuthorRole) {
return eventHandler.getAuthor(isPending);
return QVariant::fromValue(eventHandler.getAuthor(isPending));
}
if (role == HighlightRole) {
@@ -539,7 +539,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == ReadMarkersRole) {
return eventHandler.getReadMarkers();
return QVariant::fromValue(eventHandler.getReadMarkers());
}
if (role == ExcessReadMarkersRole) {
@@ -592,7 +592,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == IsEditableRole) {
return eventHandler.messageComponentType() == MessageComponentType::Text && evt.senderId() == m_currentRoom->localUser()->id();
return eventHandler.messageComponentType() == MessageComponentType::Text && evt.senderId() == m_currentRoom->localMember().id();
}
return {};

View File

@@ -116,7 +116,7 @@ void NotificationsModel::loadData()
if (!room) {
continue;
}
auto u = room->memberAvatarUrl(authorId);
auto u = room->member(authorId).avatarUrl();
auto avatar = u.isEmpty() ? QUrl() : connection()->makeMediaUrl(u);
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == QStringLiteral("mxc") ? avatar : QUrl();
@@ -125,9 +125,9 @@ void NotificationsModel::loadData()
beginInsertRows({}, m_notifications.length(), m_notifications.length());
m_notifications += Notification{
.roomId = notification.roomId,
.text = room->htmlSafeMemberName(authorId) + (roomEvent->is<StateEvent>() ? QStringLiteral(" ") : QStringLiteral(": "))
.text = room->member(authorId).htmlSafeDisplayName() + (roomEvent->is<StateEvent>() ? QStringLiteral(" ") : QStringLiteral(": "))
+ eventHandler.getPlainBody(true),
.authorName = room->htmlSafeMemberName(authorId),
.authorName = room->member(authorId).htmlSafeDisplayName(),
.authorAvatar = authorAvatar,
.eventId = roomEvent->id(),
.roomDisplayName = room->displayName(),

View File

@@ -15,7 +15,7 @@
#include <KLocalizedString>
#include <Quotient/user.h>
#include <Quotient/roommember.h>
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
: QAbstractListModel(nullptr)
@@ -69,8 +69,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
text += i18nc("Separate the usernames of users", " and ");
}
}
auto displayName = reaction.authors.at(i).toMap()[QStringLiteral("displayName")].toString();
text += displayName.isEmpty() ? reaction.authors.at(i).toMap()[QStringLiteral("id")].toString() : displayName;
text += reaction.authors.at(i).displayName();
}
if (reaction.authors.count() > 3) {
@@ -86,13 +85,9 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
return text;
}
if (role == AuthorsRole) {
return reaction.authors;
}
if (role == HasLocalUser) {
if (role == HasLocalMember) {
for (auto author : reaction.authors) {
if (author.toMap()[QStringLiteral("id")] == m_room->localUser()->id()) {
if (author.id() == m_room->localMember().id()) {
return true;
}
}
@@ -121,13 +116,13 @@ void ReactionModel::updateReactions()
return;
};
QMap<QString, QList<Quotient::User *>> reactions = {};
QMap<QString, QList<Quotient::RoomMember>> reactions = {};
for (const auto &a : annotations) {
if (a->isRedacted()) { // Just in case?
continue;
}
if (const auto &e = eventCast<const Quotient::ReactionEvent>(a)) {
reactions[e->key()].append(m_room->user(e->senderId()));
reactions[e->key()].append(m_room->member(e->senderId()));
if (e->contentJson()[QStringLiteral("shortcode")].toString().length()) {
m_shortcodes[e->key()] = e->contentJson()[QStringLiteral("shortcode")].toString().toHtmlEscaped();
}
@@ -138,15 +133,14 @@ void ReactionModel::updateReactions()
endResetModel();
return;
}
auto i = reactions.constBegin();
while (i != reactions.constEnd()) {
QVariantList authors;
for (const auto &author : i.value()) {
authors.append(m_room->getUser(author));
QList<Quotient::RoomMember> members;
for (const auto &member : i.value()) {
members.append(member);
}
m_reactions.append(ReactionModel::Reaction{i.key(), authors});
m_reactions.append(ReactionModel::Reaction{i.key(), members});
++i;
}
@@ -159,8 +153,7 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
{TextContentRole, "textContent"},
{ReactionRole, "reaction"},
{ToolTipRole, "toolTip"},
{AuthorsRole, "authors"},
{HasLocalUser, "hasLocalUser"},
{HasLocalMember, "hasLocalMember"},
};
}

View File

@@ -5,13 +5,8 @@
#include "neochatroom.h"
#include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/events/reactionevent.h>
namespace Quotient
{
class User;
}
#include <Quotient/roommember.h>
/**
* @class ReactionModel
@@ -30,7 +25,7 @@ public:
*/
struct Reaction {
QString reaction; /**< The reaction emoji. */
QVariantList authors; /**< The list of authors who sent the given reaction. */
QList<Quotient::RoomMember> authors; /**< The list of authors who sent the given reaction. */
};
/**
@@ -40,8 +35,7 @@ public:
TextContentRole = Qt::DisplayRole, /**< The text to show in the reaction. */
ReactionRole, /**< The reaction emoji. */
ToolTipRole, /**< The tool tip to show for the reaction. */
AuthorsRole, /**< The list of authors who sent the given reaction. */
HasLocalUser, /**< Whether the local user is in the list of authors. */
HasLocalMember, /**< Whether the local member is in the list of authors. */
};
explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room);

View File

@@ -85,7 +85,7 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
case ShowAuthorRole:
return true;
case AuthorRole:
return eventHandler.getAuthor();
return QVariant::fromValue(eventHandler.getAuthor());
case ShowSectionRole:
if (row == 0) {
return true;

View File

@@ -26,18 +26,26 @@ void UserListModel::setRoom(NeoChatRoom *room)
if (m_currentRoom) {
m_currentRoom->disconnect(this);
m_currentRoom->connection()->disconnect(this);
}
m_currentRoom = room;
if (m_currentRoom) {
connect(m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded);
connect(m_currentRoom, &Room::userRemoved, this, &UserListModel::userRemoved);
connect(m_currentRoom, &Room::memberAboutToRename, this, &UserListModel::userRemoved);
connect(m_currentRoom, &Room::memberRenamed, this, &UserListModel::userAdded);
connect(m_currentRoom, &Room::changed, this, &UserListModel::refreshAllUsers);
connect(m_currentRoom, &Room::memberJoined, this, &UserListModel::memberJoined);
connect(m_currentRoom, &Room::memberLeft, this, &UserListModel::memberLeft);
connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) {
refreshMember(member, {DisplayNameRole});
});
connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
refreshMember(member, {AvatarRole});
});
connect(m_currentRoom, &Room::changed, this, &UserListModel::refreshAllMembers);
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
setRoom(nullptr);
});
}
refreshAllUsers();
refreshAllMembers();
Q_EMIT roomChanged();
}
@@ -46,44 +54,36 @@ NeoChatRoom *UserListModel::room() const
return m_currentRoom;
}
Quotient::User *UserListModel::userAt(QModelIndex index) const
{
if (index.row() < 0 || index.row() >= m_users.size()) {
return nullptr;
}
return m_users.at(index.row());
}
QVariant UserListModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return QVariant();
}
if (index.row() >= m_users.count()) {
if (index.row() >= m_members.count()) {
qDebug() << "UserListModel, something's wrong: index.row() >= "
"users.count()";
return {};
}
auto user = m_users.at(index.row());
auto member = m_members.at(index.row());
if (role == DisplayNameRole) {
return user->displayname(m_currentRoom);
return member.disambiguatedName();
}
if (role == UserIdRole) {
return user->id();
return member.id();
}
if (role == AvatarRole) {
return m_currentRoom->avatarForMember(user);
return member.avatarUrl();
}
if (role == ObjectRole) {
return QVariant::fromValue(user);
return QVariant::fromValue(member);
}
if (role == PowerLevelRole) {
auto plEvent = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
if (!plEvent) {
return 0;
}
return plEvent->powerLevelForUser(user->id());
return plEvent->powerLevelForUser(member.id());
}
if (role == PowerLevelStringRole) {
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
@@ -93,7 +93,7 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
return QStringLiteral("Not Available");
}
auto userPl = pl->powerLevelForUser(user->id());
auto userPl = pl->powerLevelForUser(member.id());
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
"%1 (%2)",
@@ -109,79 +109,63 @@ int UserListModel::rowCount(const QModelIndex &parent) const
if (parent.isValid()) {
return 0;
}
return m_users.count();
return m_members.count();
}
bool UserListModel::event(QEvent *event)
{
if (event->type() == QEvent::ApplicationPaletteChange) {
refreshAllUsers();
refreshAllMembers();
}
return QObject::event(event);
}
void UserListModel::userAdded(Quotient::User *user)
void UserListModel::memberJoined(const Quotient::RoomMember &member)
{
auto pos = findUserPos(user);
auto pos = findUserPos(member);
beginInsertRows(QModelIndex(), pos, pos);
m_users.insert(pos, user);
m_members.insert(pos, member);
endInsertRows();
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
refreshUser(user, {AvatarRole});
});
}
void UserListModel::userRemoved(Quotient::User *user)
void UserListModel::memberLeft(const Quotient::RoomMember &member)
{
auto pos = findUserPos(user);
if (pos != m_users.size()) {
auto pos = findUserPos(member);
if (pos != m_members.size()) {
beginRemoveRows(QModelIndex(), pos, pos);
m_users.removeAt(pos);
m_members.removeAt(pos);
endRemoveRows();
user->disconnect(this);
} else {
qWarning() << "Trying to remove a room member not in the user list";
}
}
void UserListModel::refreshUser(Quotient::User *user, const QList<int> &roles)
void UserListModel::refreshMember(const Quotient::RoomMember &member, const QList<int> &roles)
{
auto pos = findUserPos(user);
if (pos != m_users.size()) {
auto pos = findUserPos(member);
if (pos != m_members.size()) {
Q_EMIT dataChanged(index(pos), index(pos), roles);
} else {
qWarning() << "Trying to access a room member not in the user list";
}
}
void UserListModel::refreshAllUsers()
void UserListModel::refreshAllMembers()
{
beginResetModel();
for (User *user : std::as_const(m_users)) {
user->disconnect(this);
}
m_users.clear();
m_members.clear();
if (m_currentRoom != nullptr) {
m_users = m_currentRoom->users();
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
for (User *user : std::as_const(m_users)) {
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
refreshUser(user, {AvatarRole});
});
}
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
setRoom(nullptr);
});
m_members = m_currentRoom->members();
std::sort(m_members.begin(), m_members.end(), m_currentRoom->memberSorter());
}
endResetModel();
Q_EMIT usersRefreshed();
}
int UserListModel::findUserPos(Quotient::User *user) const
int UserListModel::findUserPos(const RoomMember &member) const
{
return findUserPos(m_currentRoom->safeMemberName(user->id()));
return findUserPos(member.displayName());
}
int UserListModel::findUserPos(const QString &username) const
@@ -189,7 +173,7 @@ int UserListModel::findUserPos(const QString &username) const
if (!m_currentRoom) {
return 0;
}
return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username);
return m_currentRoom->memberSorter().lowerBoundIndex(m_members, username);
}
QHash<int, QByteArray> UserListModel::roleNames() const

View File

@@ -56,11 +56,6 @@ public:
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
/**
* @brief The user at the given index of the model.
*/
[[nodiscard]] Quotient::User *userAt(QModelIndex index) const;
/**
* @brief Get the given role value at the given index.
*
@@ -90,15 +85,15 @@ protected:
bool event(QEvent *event) override;
private Q_SLOTS:
void userAdded(Quotient::User *user);
void userRemoved(Quotient::User *user);
void refreshUser(Quotient::User *user, const QList<int> &roles = {});
void refreshAllUsers();
void memberJoined(const Quotient::RoomMember &member);
void memberLeft(const Quotient::RoomMember &member);
void refreshMember(const Quotient::RoomMember &member, const QList<int> &roles = {});
void refreshAllMembers();
private:
QPointer<NeoChatRoom> m_currentRoom;
QList<Quotient::User *> m_users;
QList<Quotient::RoomMember> m_members;
int findUserPos(Quotient::User *user) const;
int findUserPos(const Quotient::RoomMember &member) const;
[[nodiscard]] int findUserPos(const QString &username) const;
};

View File

@@ -11,9 +11,9 @@
#include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h>
#include <Quotient/user.h>
#include <qcoro/qcorosignal.h>
#include <Quotient/avatar.h>
#include <Quotient/connection.h>
#include <Quotient/csapi/account-data.h>
#include <Quotient/csapi/directory.h>
@@ -107,15 +107,15 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
if (this->joinState() != JoinState::Invite) {
return;
}
auto roomMemberEvent = currentState().get<RoomMemberEvent>(localUser()->id());
auto roomMemberEvent = currentState().get<RoomMemberEvent>(localMember().id());
QImage avatar_image;
if (roomMemberEvent && !user(roomMemberEvent->senderId())->avatarUrl(this).isEmpty()) {
avatar_image = user(roomMemberEvent->senderId())->avatar(128, this);
if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {});
} else {
qWarning() << "using this room's avatar";
avatar_image = avatar(128);
}
NotificationsManager::instance().postInviteNotification(this, displayName(), htmlSafeMemberName(roomMemberEvent->senderId()), avatar_image);
NotificationsManager::instance().postInviteNotification(this, displayName(), member(roomMemberEvent->senderId()).htmlSafeDisplayName(), avatar_image);
},
Qt::SingleShotConnection);
connect(this, &Room::changed, this, [this] {
@@ -264,18 +264,16 @@ void NeoChatRoom::forget()
QVariantList NeoChatRoom::getUsersTyping() const
{
auto users = usersTyping();
users.removeAll(localUser());
auto members = membersTyping();
members.removeAll(localMember());
QVariantList userVariants;
for (const auto &user : users) {
if (connection()->isIgnored(user->id())) {
for (const auto &member : members) {
if (connection()->isIgnored(member.id())) {
continue;
}
userVariants.append(QVariantMap{
{"id"_ls, user->id()},
{"avatarMediaId"_ls, user->avatarMediaId(this)},
{"displayName"_ls, user->displayname(this)},
{"display"_ls, user->name()},
{"id"_ls, member.id()},
{"displayName"_ls, member.displayName()},
});
}
return userVariants;
@@ -283,7 +281,7 @@ QVariantList NeoChatRoom::getUsersTyping() const
void NeoChatRoom::sendTypingNotification(bool isTyping)
{
connection()->callApi<SetTypingJob>(BackgroundRequest, localUser()->id(), id(), isTyping, 10000);
connection()->callApi<SetTypingJob>(BackgroundRequest, localMember().id(), id(), isTyping, 10000);
}
const RoomEvent *NeoChatRoom::lastEvent() const
@@ -321,7 +319,7 @@ const RoomEvent *NeoChatRoom::lastEvent() const
}
}
if (connection()->isIgnored(user(event->senderId()))) {
if (connection()->isIgnored(event->senderId())) {
continue;
}
@@ -381,13 +379,13 @@ bool NeoChatRoom::isEventHighlighted(const RoomEvent *e) const
void NeoChatRoom::checkForHighlights(const Quotient::TimelineItem &ti)
{
auto localUserId = localUser()->id();
if (ti->senderId() == localUserId) {
auto localMember = this->localMember();
if (ti->senderId() == localMember.id()) {
return;
}
if (auto *e = ti.viewAs<RoomMessageEvent>()) {
const auto &text = e->plainBody();
if (text.contains(localUserId) || text.contains(safeMemberName(localUserId))) {
if (text.contains(localMember.id()) || text.contains(localMember.disambiguatedName())) {
highlights.insert(e);
}
}
@@ -433,40 +431,6 @@ QDateTime NeoChatRoom::lastActiveTime()
return messageEvents().rbegin()->get()->originTimestamp();
}
// An empty user is useful for returning as a model value to avoid properties being undefined.
static const QVariantMap emptyUser = {
{"isLocalUser"_ls, false},
{"id"_ls, QString()},
{"displayName"_ls, QString()},
{"avatarSource"_ls, QUrl()},
{"avatarMediaId"_ls, QString()},
{"color"_ls, QColor()},
{"object"_ls, QVariant()},
};
QVariantMap NeoChatRoom::getUser(const QString &userID) const
{
return getUser(user(userID));
}
QVariantMap NeoChatRoom::getUser(User *user) const
{
if (user == nullptr) {
return emptyUser;
}
return QVariantMap{
{QStringLiteral("isLocalUser"), user->id() == localUser()->id()},
{QStringLiteral("id"), user->id()},
{QStringLiteral("displayName"), user->displayname(this)},
{QStringLiteral("escapedDisplayName"), htmlSafeMemberName(user->id())},
{QStringLiteral("avatarSource"), avatarForMember(user)},
{QStringLiteral("avatarMediaId"), user->avatarMediaId(this)},
{QStringLiteral("color"), Utils::getUserColor(user->hueF())},
{QStringLiteral("object"), QVariant::fromValue(user)},
};
}
QString NeoChatRoom::avatarMediaId() const
{
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
@@ -474,10 +438,10 @@ QString NeoChatRoom::avatarMediaId() const
}
// Use the first (excluding self) user's avatar for direct chats
const auto dcUsers = directChatUsers();
for (const auto u : dcUsers) {
if (u != localUser()) {
return u->avatarMediaId(this);
const auto directChatMembers = this->directChatMembers();
for (const auto member : directChatMembers) {
if (member != localMember()) {
return member.avatarMediaId();
}
}
@@ -644,7 +608,7 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction
continue;
}
if (e->senderId() == localUser()->id()) {
if (e->senderId() == localMember().id()) {
redactEventIds.push_back(e->id());
break;
}
@@ -673,7 +637,7 @@ bool NeoChatRoom::canSendEvent(const QString &eventType) const
return false;
}
auto pl = plEvent->powerLevelForEvent(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
auto currentPl = plEvent->powerLevelForUser(localMember().id());
return currentPl >= pl;
}
@@ -685,7 +649,7 @@ bool NeoChatRoom::canSendState(const QString &eventType) const
return false;
}
auto pl = plEvent->powerLevelForState(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
auto currentPl = plEvent->powerLevelForUser(localMember().id());
return currentPl >= pl;
}
@@ -863,7 +827,7 @@ void NeoChatRoom::setUrlPreviewEnabled(const bool &urlPreviewEnabled)
* "type": "org.matrix.room.preview_urls",
* }
*/
connection()->callApi<SetAccountDataPerRoomJob>(localUser()->id(),
connection()->callApi<SetAccountDataPerRoomJob>(localMember().id(),
id(),
"org.matrix.room.preview_urls"_ls,
QJsonObject{{"disable"_ls, !urlPreviewEnabled}});
@@ -1531,7 +1495,7 @@ void NeoChatRoom::editLastMessage()
}
// check if the current message's sender's id is same as the user's id
if ((*it)->senderId() == localUser()->id()) {
if ((*it)->senderId() == localMember().id()) {
auto content = (*it)->contentJson();
if (e->msgtype() != MessageEventType::Unknown) {
@@ -1656,13 +1620,9 @@ int NeoChatRoom::maxRoomVersion() const
return maxVersion;
}
Quotient::User *NeoChatRoom::directChatRemoteUser() const
Quotient::RoomMember NeoChatRoom::directChatRemoteMember() const
{
auto users = connection()->directChatUsers(this);
if (users.isEmpty()) {
return nullptr;
}
return users[0];
return directChatMembers()[0];
}
void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)
@@ -1694,20 +1654,6 @@ QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType)
return QJsonDocument(accountData(eventType)->fullJson()).toJson();
}
QUrl NeoChatRoom::avatarForMember(Quotient::User *user) const
{
const auto &url = memberAvatarUrl(user->id());
if (url.isEmpty() || url.scheme() != "mxc"_ls) {
return {};
}
auto avatar = connection()->makeMediaUrl(url);
if (avatar.isValid() && avatar.scheme() == QStringLiteral("mxc")) {
return avatar;
} else {
return QUrl();
}
}
void NeoChatRoom::downloadEventFromServer(const QString &eventId)
{
if (findInTimeline(eventId) != historyEdge()) {
@@ -1779,10 +1725,9 @@ void NeoChatRoom::cleanupExtraEvent(const QString &eventId)
m_extraEvents.erase(it);
}
}
User *NeoChatRoom::invitingUser() const
QString NeoChatRoom::invitingUserId() const
{
return connection()->user(currentState().get<RoomMemberEvent>(connection()->userId())->senderId());
return currentState().get<RoomMemberEvent>(connection()->userId())->senderId();
}
void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, const QByteArray &content)

View File

@@ -10,7 +10,7 @@
#include <QQmlEngine>
#include <QCoroTask>
#include <Quotient/user.h>
#include <Quotient/roommember.h>
#include "enums/pushrule.h"
#include "pollhandler.h"
@@ -93,9 +93,9 @@ class NeoChatRoom : public Quotient::Room
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
/**
* @brief Get a user object for the other person in a direct chat.
* @brief Get a RoomMember object for the other person in a direct chat.
*/
Q_PROPERTY(Quotient::User *directChatRemoteUser READ directChatRemoteUser CONSTANT)
Q_PROPERTY(Quotient::RoomMember directChatRemoteMember READ directChatRemoteMember CONSTANT)
/**
* @brief The Matrix IDs of this room's parents.
@@ -235,58 +235,6 @@ public:
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {});
/**
* @brief Get a user in the context of this room.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* provides the room context and outputs the result as QVariantMap.
*
* Can be called with an empty QString to return an empty user, which is a useful return
* from models to avoid undefined properties.
*
* @param userID the ID of the user to output.
*
* @return a QVariantMap for the user with the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa Quotient::User
*/
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const;
/**
* @brief Get a user in the context of this room.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room. This function
* provides the room context and outputs the result as QVariantMap.
*
* Can be called with a nullptr to return an empty user, which is a useful return
* from models to avoid undefined properties.
*
* @param user the user to output.
*
* @return a QVariantMap for the user with the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa Quotient::User
*/
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(Quotient::User *user) const;
[[nodiscard]] QVariantList getUsersTyping() const;
[[nodiscard]] QDateTime lastActiveTime();
@@ -400,7 +348,7 @@ public:
[[nodiscard]] QString avatarMediaId() const;
Quotient::User *directChatRemoteUser() const;
Quotient::RoomMember directChatRemoteMember() const;
/**
* @brief Whether this room has one or more parent spaces set.
@@ -630,8 +578,6 @@ public:
*/
Q_INVOKABLE QByteArray roomAcountDataJson(const QString &eventType);
Q_INVOKABLE [[nodiscard]] QUrl avatarForMember(Quotient::User *user) const;
/**
* @brief Loads the event with the given id from the server and saves it locally.
*
@@ -660,7 +606,7 @@ public:
/**
* If we're invited to this room, the user that invited us. Undefined in other cases.
*/
Q_INVOKABLE Quotient::User *invitingUser() const;
Q_INVOKABLE QString invitingUserId() const;
private:
QSet<const Quotient::RoomEvent *> highlights;

View File

@@ -108,7 +108,7 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
if (!room) {
continue;
}
auto sender = room->user(notification["event"_ls]["sender"_ls].toString());
auto sender = room->member(notification["event"_ls]["sender"_ls].toString());
QString body;
if (notification["event"_ls]["type"_ls].toString() == "org.matrix.msc3381.poll.start"_ls) {
@@ -124,13 +124,13 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
}
QImage avatar_image;
if (!sender->avatarUrl(room).isEmpty()) {
avatar_image = sender->avatar(128, room);
if (!sender.avatarUrl().isEmpty()) {
avatar_image = room->memberAvatar(sender.id()).get(connection, 128, {});
} else {
avatar_image = room->avatar(128);
}
postNotification(dynamic_cast<NeoChatRoom *>(room),
sender->displayname(room),
sender.displayName(),
body,
avatar_image,
notification["event"_ls].toObject()["event_id"_ls].toString(),
@@ -213,7 +213,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
if (!room) {
return;
}
auto connection = dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().get(room->localUser()->id()));
auto connection = dynamic_cast<NeoChatConnection *>(Controller::instance().accounts().get(room->localMember().id()));
Controller::instance().setActiveConnection(connection);
RoomManager::instance().setConnection(connection);
RoomManager::instance().resolveResource(room->id());
@@ -230,7 +230,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
notification->setReplyAction(std::move(replyAction));
}
notification->setHint(QStringLiteral("x-kde-origin-name"), room->localUser()->id());
notification->setHint(QStringLiteral("x-kde-origin-name"), room->localMember().id());
notification->sendEvent();
}
@@ -276,7 +276,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QS
return;
}
RoomManager::instance().leaveRoom(room);
room->connection()->addToIgnoredUsers(room->invitingUser());
room->connection()->addToIgnoredUsers(room->invitingUserId());
notification->close();
});
connect(notification, &KNotification::closed, this, [this, room]() {
@@ -286,7 +286,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QS
m_invitations.remove(room->id());
});
notification->setHint(QStringLiteral("x-kde-origin-name"), room->localUser()->id());
notification->setHint(QStringLiteral("x-kde-origin-name"), room->localMember().id());
notification->sendEvent();
}

View File

@@ -154,7 +154,7 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
return;
}
QStringList ownAnswers;
for (const auto &answer : m_answers[room->localUser()->id()].toArray()) {
for (const auto &answer : m_answers[room->localMember().id()].toArray()) {
ownAnswers += answer.toString();
}
if (ownAnswers.contains(answerId)) {
@@ -169,7 +169,7 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
}
auto response = new PollResponseEvent(eventId, ownAnswers);
handleAnswer(response->contentJson(), room->localUser()->id(), QDateTime::currentDateTime());
handleAnswer(response->contentJson(), room->localMember().id(), QDateTime::currentDateTime());
room->postEvent(response);
}

View File

@@ -49,7 +49,7 @@ Loader {
text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard")
icon.name: "edit-copy"
onTriggered: if (room.isDirectChat()) {
Clipboard.saveText(room.directChatRemoteUser.id);
Clipboard.saveText(room.directChatRemoteMember.id);
} else if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id);
} else {

View File

@@ -40,18 +40,9 @@ Loader {
/**
* @brief The message author.
*
* This should consist of the following:
* - id - The matrix ID of the author.
* - isLocalUser - Whether the author is the local user.
* - avatarSource - The mxc URL for the author's avatar in the current room.
* - avatarMediaId - The media ID of the author's avatar.
* - avatarUrl - The mxc URL for the author's avatar.
* - displayName - The display name of the author.
* - display - The name of the author.
* - color - The color for the author.
* - object - The Quotient::User object for the author.
* A Quotient::RoomMember object.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
required property var author
@@ -90,7 +81,7 @@ Loader {
}
component RemoveMessageAction: Kirigami.Action {
visible: author.isLocalUser || currentRoom.canSendState("redact")
visible: author.isLocalMember || currentRoom.canSendState("redact")
text: i18n("Remove")
icon.name: "edit-delete-remove"
icon.color: "red"
@@ -116,7 +107,7 @@ Loader {
component ReportMessageAction: Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic"
visible: !author.isLocalUser
visible: !author.isLocalMember
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReportSheet'), {
room: currentRoom,
eventId: eventId

View File

@@ -33,7 +33,7 @@ ColumnLayout {
Layout.alignment: Qt.AlignHCenter
onClicked: {
RoomManager.resolveResource(root.room.directChatRemoteUser.id, "mention");
RoomManager.resolveResource(root.room.directChatRemoteMember.uri)
}
contentItem: KirigamiComponents.Avatar {

View File

@@ -63,7 +63,7 @@ DelegateContextMenu {
}
},
Kirigami.Action {
visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact")
visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact")
text: i18n("Remove")
icon.name: "edit-delete-remove"
icon.color: "red"

View File

@@ -22,7 +22,7 @@ Kirigami.PlaceholderMessage {
onClicked: {
RoomManager.leaveRoom(root.currentRoom);
root.currentRoom.connection.addToIgnoredUsers(root.currentRoom.invitingUser());
root.currentRoom.connection.addToIgnoredUsers(root.currentRoom.invitingUserId());
}
}
QQC2.Button {

View File

@@ -55,7 +55,7 @@ MapQuickItem {
width: height
height: parent.height / 3 + 1
name: root.author.displayName
source: root.author.avatarSource
source: root.author.avatarUrl
color: root.author.color
}

View File

@@ -354,7 +354,7 @@ Kirigami.ApplicationWindow {
function showUserDetail(user) {
Qt.createComponent("org.kde.neochat", "UserDetailDialog").createObject(root.QQC2.ApplicationWindow.window, {
room: RoomManager.currentRoom ? RoomManager.currentRoom : null,
user: RoomManager.currentRoom ? RoomManager.currentRoom.getUser(user.id) : QmlUtils.getUser(user),
user: user,
connection: root.connection
}).open();
}

View File

@@ -40,7 +40,7 @@ DelegateContextMenu {
currentRoom.editCache.editId = eventId;
currentRoom.mainCache.replyId = "";
}
visible: author.isLocalUser && (root.messageComponentType === MessageComponentType.Emote || root.messageComponentType === MessageComponentType.Message)
visible: author.isLocalMember && (root.messageComponentType === MessageComponentType.Emote || root.messageComponentType === MessageComponentType.Message)
},
DelegateContextMenu.ReplyMessageAction {},
Kirigami.Action {

View File

@@ -19,8 +19,21 @@ Kirigami.Dialog {
// This dialog is sometimes used outside the context of a room, e.g., when scanning a user's QR code.
// Make sure that code is prepared to deal with this property being null
property NeoChatRoom room
/**
* @brief The user's profile object.
*
* Required to interact with the profile and perform action like ignoring.
*/
property var user
/**
* @brief The RoomMember object for the user in the current room.
*
* Required to visualise the user.
*/
property var member: root.room.member(user.id)
property NeoChatConnection connection
leftPadding: 0
@@ -48,9 +61,9 @@ Kirigami.Dialog {
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
name: root.user.displayName
source: root.user.avatarSource
color: root.user.color
name: root.member.displayName
source: root.member.avatarUrl
color: root.member.color
}
ColumnLayout {
@@ -69,7 +82,7 @@ Kirigami.Dialog {
Kirigami.SelectableLabel {
textFormat: TextEdit.PlainText
text: root.user.id
text: root.member.id
}
}
QQC2.AbstractButton {
@@ -78,16 +91,16 @@ Kirigami.Dialog {
contentItem: Barcode {
id: barcode
barcodeType: Barcode.QRCode
content: "https://matrix.to/#/" + root.user.id
content: "https://matrix.to/#/" + root.member.id
}
onClicked: {
let map = qrMaximizeComponent.createObject(parent, {
text: barcode.content,
title: root.user.displayName,
subtitle: root.user.id,
avatarColor: root.user.color,
avatarSource: root.user.avatarSource
title: root.member.displayName,
subtitle: root.member.id,
avatarColor: root.member.color,
avatarSource: root.member.avatarUrl,
});
root.close();
map.open();
@@ -104,46 +117,46 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: !root.user.isLocalUser && !!root.user.object
visible: !root.member.isLocalMember
action: Kirigami.Action {
text: !!root.user.object && root.connection.isIgnored(root.user.object) ? i18n("Unignore this user") : i18n("Ignore this user")
text: !!root.user && root.connection.isIgnored(root.user) ? i18n("Unignore this user") : i18n("Ignore this user")
icon.name: "im-invisible-user"
onTriggered: {
root.close();
root.connection.isIgnored(root.user.object) ? root.connection.removeFromIgnoredUsers(root.user.object) : root.connection.addToIgnoredUsers(root.user.object);
root.connection.isIgnored(root.user) ? root.connection.removeFromIgnoredUsers(root.user) : root.connection.addToIgnoredUsers(root.user);
}
}
}
FormCard.FormButtonDelegate {
visible: root.room && !root.user.isLocalUser && room.canSendState("kick") && room.containsUser(root.user.id) && room.getUserPowerLevel(root.user.id) < room.getUserPowerLevel(root.connection.localUser.id)
visible: root.room && !root.member.isLocalMember && room.canSendState("kick") && room.containsUser(root.member.id) && room.getUserPowerLevel(root.member.id) < room.getUserPowerLevel(root.room.localMember.id)
action: Kirigami.Action {
text: i18n("Kick this user")
icon.name: "im-kick-user"
onTriggered: {
root.room.kickMember(root.user.id);
root.room.kickMember(root.member.id);
root.close();
}
}
}
FormCard.FormButtonDelegate {
visible: root.room && !root.user.isLocalUser && room.canSendState("invite") && !room.containsUser(root.user.id)
visible: root.room && !root.member.isLocalMember && room.canSendState("invite") && !room.containsUser(root.member.id)
action: Kirigami.Action {
enabled: root.room && !root.room.isUserBanned(root.user.id)
enabled: root.room && !root.room.isUserBanned(root.member.id)
text: i18n("Invite this user")
icon.name: "list-add-user"
onTriggered: {
root.room.inviteToRoom(root.user.id);
root.room.inviteToRoom(root.member.id);
root.close();
}
}
}
FormCard.FormButtonDelegate {
visible: root.room && !root.user.isLocalUser && room.canSendState("ban") && !room.isUserBanned(root.user.id) && room.getUserPowerLevel(root.user.id) < room.getUserPowerLevel(root.room.connection.localUser.id)
visible: root.room && !root.member.isLocalMember && room.canSendState("ban") && !room.isUserBanned(root.member.id) && room.getUserPowerLevel(root.member.id) < room.getUserPowerLevel(root.room.localMember.id)
action: Kirigami.Action {
text: i18n("Ban this user")
@@ -152,7 +165,7 @@ Kirigami.Dialog {
onTriggered: {
(root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'BanSheet'), {
room: root.room,
userId: root.user.id
userId: root.member.id
}, {
title: i18nc("@title", "Ban User"),
width: Kirigami.Units.gridUnit * 25
@@ -163,14 +176,14 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: root.room && !root.user.isLocalUser && room.canSendState("ban") && room.isUserBanned(root.user.id)
visible: root.room && !root.member.isLocalMember && room.canSendState("ban") && room.isUserBanned(root.member.id)
action: Kirigami.Action {
text: i18n("Unban this user")
icon.name: "im-irc"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
root.room.unban(root.user.id);
root.room.unban(root.member.id);
root.close();
}
}
@@ -184,8 +197,8 @@ Kirigami.Dialog {
onTriggered: {
let dialog = powerLevelDialog.createObject(this, {
room: root.room,
userId: root.user.id,
powerLevel: root.room.getUserPowerLevel(root.user.id)
userId: root.member.id,
powerLevel: root.room.getUserPowerLevel(root.member.id)
});
dialog.open();
root.close();
@@ -201,7 +214,7 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: root.room && (root.user.isLocalUser || room.canSendState("redact"))
visible: root.room && (root.member.isLocalUser || room.canSendState("redact"))
action: Kirigami.Action {
text: i18n("Remove recent messages by this user")
@@ -210,7 +223,7 @@ Kirigami.Dialog {
onTriggered: {
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), {
room: root.room,
userId: root.user.id
userId: root.member.id
}, {
title: i18nc("@title", "Remove Messages"),
width: Kirigami.Units.gridUnit * 25
@@ -221,12 +234,12 @@ Kirigami.Dialog {
}
FormCard.FormButtonDelegate {
visible: !root.user.isLocalUser
visible: !root.member.isLocalMember
action: Kirigami.Action {
text: root.connection.directChatExists(root.user.object) ? i18nc("%1 is the name of the user.", "Chat with %1", root.user.escapedDisplayName) : i18n("Invite to private chat")
icon.name: "document-send"
onTriggered: {
root.connection.openOrCreateDirectChat(root.user.object);
root.room.connection.openOrCreateDirectChat(root.user);
root.close();
}
}
@@ -237,7 +250,7 @@ Kirigami.Dialog {
text: i18n("Copy link")
icon.name: "username-copy"
onTriggered: {
Clipboard.saveText("https://matrix.to/#/" + root.user.id);
Clipboard.saveText("https://matrix.to/#/" + root.member.id);
}
}
}

View File

@@ -256,7 +256,6 @@ void RoomManager::openRoomForActiveConnection()
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
{
if (action == "mention"_ls || action.isEmpty()) {
// send it has QVariantMap because the properties in the
user->load();
Q_EMIT showUserDetail(user);
} else if (action == "_interactive"_ls) {

View File

@@ -8,6 +8,7 @@
#include <QObject>
#include <QQmlEngine>
#include <Quotient/room.h>
#include <Quotient/roommember.h>
#include <Quotient/uriresolver.h>
#include "chatdocumenthandler.h"
@@ -290,7 +291,7 @@ Q_SIGNALS:
* @brief Request to show a menu for the given event.
*/
void showMessageMenu(const QString &eventId,
const QVariantMap &author,
const Quotient::RoomMember &author,
MessageComponentType::Type messageComponentType,
const QString &plainText,
const QString &htmlText,
@@ -300,7 +301,7 @@ Q_SIGNALS:
* @brief Request to show a menu for the given media event.
*/
void showFileMenu(const QString &eventId,
const QVariantMap &author,
const Quotient::RoomMember &author,
MessageComponentType::Type messageComponentType,
const QString &plainText,
const QString &mimeType,

View File

@@ -61,8 +61,8 @@ FormCard.FormCardPage {
spacing: Kirigami.Units.largeSpacing
QQC2.Label {
id: powerLevelLabel
visible: !room.canSendState("m.room.power_levels") || (room.getUserPowerLevel(room.localUser.id) <= privilegedUserDelegate.powerLevel && privilegedUserDelegate.userId != room.localUser.id)
text: privilegedUserDelegate.powerLevelString
visible: !room.canSendState("m.room.power_levels") || (room.getUserPowerLevel(room.localMember.id) <= privilegedUserDelegate.powerLevel && privilegedUserDelegate.userId != room.localMember.id)
color: Kirigami.Theme.disabledTextColor
}
QQC2.ComboBox {

View File

@@ -691,9 +691,9 @@ QString TextHandler::emoteString(const NeoChatRoom *room, const Quotient::RoomEv
}
auto e = eventCast<const Quotient::RoomMessageEvent>(event);
auto author = room->user(e->senderId());
return QStringLiteral("* <a href=\"https://matrix.to/#/") + e->senderId() + QStringLiteral("\" style=\"color:") + Utils::getUserColor(author->hueF()).name()
+ QStringLiteral("\">") + author->displayname(room) + QStringLiteral("</a> ");
auto author = room->member(e->senderId());
return QStringLiteral("* <a href=\"https://matrix.to/#/") + e->senderId() + QStringLiteral("\" style=\"color:") + author.color().name()
+ QStringLiteral("\">") + author.htmlSafeDisplayName() + QStringLiteral("</a> ");
}
QString TextHandler::convertCodeLanguageString(const QString &languageString)

View File

@@ -25,7 +25,7 @@ Flow {
implicitHeight: root.avatarSize
name: modelData.displayName
source: modelData.avatarSource
source: modelData.avatarUrl
color: modelData.color
}
}

View File

@@ -35,18 +35,9 @@ QQC2.Control {
/**
* @brief The message author.
*
* This should consist of the following:
* - id - The matrix ID of the author.
* - isLocalUser - Whether the author is the local user.
* - avatarSource - The mxc URL for the author's avatar in the current room.
* - avatarMediaId - The media ID of the author's avatar.
* - avatarUrl - The mxc URL for the author's avatar.
* - displayName - The display name of the author.
* - display - The name of the author.
* - color - The color for the author.
* - object - The Quotient::User object for the author.
* A Quotient::RoomMember object.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
property var author
@@ -125,14 +116,14 @@ QQC2.Control {
id: nameButton
Layout.fillWidth: true
contentItem: QQC2.Label {
text: root.author.displayName
text: root.author.disambiguatedName
color: root.author.color
textFormat: Text.PlainText
font.weight: Font.Bold
elide: Text.ElideRight
}
Accessible.name: contentItem.text
onClicked: RoomManager.resolveResource(root.author.id, "mention")
onClicked: RoomManager.resolveResource(root.author.uri)
}
QQC2.Label {
id: timeLabel
@@ -176,7 +167,7 @@ QQC2.Control {
visible: root.showBackground
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: if (root.author.isLocalUser) {
color: if (root.author.isLocalMember) {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15);
} else if (root.showHighlight) {
return Kirigami.Theme.positiveBackgroundColor;

View File

@@ -16,18 +16,9 @@ QQC2.Control {
/**
* @brief The message author.
*
* This should consist of the following:
* - id - The matrix ID of the author.
* - isLocalUser - Whether the author is the local user.
* - avatarSource - The mxc URL for the author's avatar in the current room.
* - avatarMediaId - The media ID of the author's avatar.
* - avatarUrl - The mxc URL for the author's avatar.
* - displayName - The display name of the author.
* - display - The name of the author.
* - color - The color for the author.
* - object - The Quotient::User object for the author.
* A Quotient::RoomMember object.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
required property var author

View File

@@ -19,18 +19,9 @@ ColumnLayout {
/**
* @brief The message author.
*
* This should consist of the following:
* - id - The matrix ID of the author.
* - isLocalUser - Whether the author is the local user.
* - avatarSource - The mxc URL for the author's avatar in the current room.
* - avatarMediaId - The media ID of the author's avatar.
* - avatarUrl - The mxc URL for the author's avatar.
* - displayName - The display name of the author.
* - display - The name of the author.
* - color - The color for the author.
* - object - The Quotient::User object for the author.
* A Quotient::RoomMember object.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
required property var author

View File

@@ -56,18 +56,9 @@ TimelineDelegate {
/**
* @brief The message author.
*
* This should consist of the following:
* - id - The matrix ID of the author.
* - isLocalUser - Whether the author is the local user.
* - avatarSource - The mxc URL for the author's avatar in the current room.
* - avatarMediaId - The media ID of the author's avatar.
* - avatarUrl - The mxc URL for the author's avatar.
* - displayName - The display name of the author.
* - display - The name of the author.
* - color - The color for the author.
* - object - The Quotient::User object for the author.
* A Quotient::RoomMember object.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
required property var author
@@ -281,11 +272,11 @@ TimelineDelegate {
visible: (root.showAuthor || root.alwaysShowAuthor) && Config.showAvatarInTimeline && (Config.compactLayout || !_private.showUserMessageOnRight)
name: root.author.displayName
source: root.author.avatarSource
source: root.author.avatarUrl
color: root.author.color
QQC2.ToolTip.text: root.author.escapedDisplayName
QQC2.ToolTip.text: root.author.htmlSafeDisambiguatedName
onClicked: RoomManager.resolveResource(root.author.id, "mention")
onClicked: RoomManager.resolveResource(root.author.uri)
}
Bubble {
id: bubble
@@ -411,7 +402,7 @@ TimelineDelegate {
/**
* @brief Whether local user messages should be aligned right.
*/
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalUser && !Config.compactLayout && !root.alwaysFillWidth
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalMember && !Config.compactLayout && !root.alwaysFillWidth
function showMessageMenu() {
RoomManager.viewEventMenu(root.eventId, root.room, root.selectedText);

View File

@@ -52,7 +52,7 @@ ColumnLayout {
delegate: RowLayout {
Layout.fillWidth: true
CheckBox {
checked: root.pollHandler.answers[root.room.localUser.id] ? root.pollHandler.answers[root.room.localUser.id].includes(modelData["id"]) : false
checked: root.pollHandler.answers[root.room.localember.id] ? root.pollHandler.answers[root.room.localMember.id].includes(modelData["id"]) : false
onClicked: root.pollHandler.sendPollAnswer(root.eventId, modelData["id"])
enabled: !root.pollHandler.hasEnded
}

View File

@@ -35,7 +35,7 @@ Flow {
required property string textContent
required property string reaction
required property string toolTip
required property bool hasLocalUser
required property bool hasLocalMember
width: Math.max(contentItem.implicitWidth + leftPadding + rightPadding, height)
height: Math.round(Kirigami.Units.gridUnit * 1.5)
@@ -54,13 +54,13 @@ Flow {
padding: Kirigami.Units.smallSpacing
background: Kirigami.ShadowedRectangle {
color: reactionDelegate.hasLocalUser ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
color: reactionDelegate.hasLocalMember ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
Kirigami.Theme.inherit: false
Kirigami.Theme.colorSet: Config.compactLayout ? Kirigami.Theme.Window : Kirigami.Theme.View
radius: height / 2
shadow {
size: Kirigami.Units.smallSpacing
color: !reactionDelegate.hasLocalUser ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
color: !reactionDelegate.hasLocalMember ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10)
}
}

View File

@@ -32,18 +32,9 @@ RowLayout {
/**
* @brief The reply author.
*
* This should consist of the following:
* - id - The matrix ID of the reply author.
* - isLocalUser - Whether the reply author is the local user.
* - avatarSource - The mxc URL for the reply author's avatar in the current room.
* - avatarMediaId - The media ID of the reply author's avatar.
* - avatarUrl - The mxc URL for the reply author's avatar.
* - displayName - The display name of the reply author.
* - display - The name of the reply author.
* - color - The color for the reply author.
* - object - The Quotient::User object for the reply author.
* A Quotient::RoomMember object.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
required property var replyAuthor
@@ -87,7 +78,7 @@ RowLayout {
implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: Kirigami.Units.iconSizes.small
source: root.replyAuthor.avatarSource
source: root.replyAuthor.avatarUrl
name: root.replyAuthor.displayName
color: root.replyAuthor.color
}

View File

@@ -24,18 +24,9 @@ RowLayout {
/**
* @brief The message author.
*
* This should consist of the following:
* - id - The matrix ID of the author.
* - isLocalUser - Whether the author is the local user.
* - avatarSource - The mxc URL for the author's avatar in the current room.
* - avatarMediaId - The media ID of the author's avatar.
* - avatarUrl - The mxc URL for the author's avatar.
* - displayName - The display name of the author.
* - display - The name of the author.
* - color - The color for the author.
* - object - The Quotient::User object for the author.
* A Quotient::RoomMember object.
*
* @sa Quotient::User
* @sa Quotient::RoomMember
*/
property var author: modelData.author

View File

@@ -113,7 +113,7 @@ TimelineDelegate {
implicitHeight: Kirigami.Units.iconSizes.small
name: parent.modelData.displayName
source: parent.modelData.avatarSource
source: parent.modelData.avatarUrl
color: parent.modelData.color
}
}

View File

@@ -7,45 +7,6 @@
#include <QJsonDocument>
using namespace Quotient;
static const QVariantMap emptyUser = {
{"isLocalUser"_ls, false},
{"id"_ls, QString()},
{"displayName"_ls, QString()},
{"avatarSource"_ls, QUrl()},
{"avatarMediaId"_ls, QString()},
{"color"_ls, QColor()},
{"object"_ls, QVariant()},
};
QVariantMap QmlUtils::getUser(User *user) const
{
if (user == nullptr) {
return emptyUser;
}
const auto &url = user->avatarUrl();
if (url.isEmpty() || url.scheme() != "mxc"_ls) {
return {};
}
auto avatarSource = user->connection()->makeMediaUrl(url);
if (!avatarSource.isValid() || avatarSource.scheme() != QStringLiteral("mxc")) {
avatarSource = {};
}
return QVariantMap{
{QStringLiteral("isLocalUser"), user->id() == user->connection()->user()->id()},
{QStringLiteral("id"), user->id()},
{QStringLiteral("displayName"), user->displayname()},
{QStringLiteral("escapedDisplayName"), user->displayname().toHtmlEscaped()},
{QStringLiteral("avatarSource"), avatarSource},
{QStringLiteral("avatarMediaId"), user->avatarMediaId()},
{QStringLiteral("color"), Utils::getUserColor(user->hueF())},
{QStringLiteral("object"), QVariant::fromValue(user)},
};
}
bool QmlUtils::isValidJson(const QByteArray &json)
{
return !QJsonDocument::fromJson(json).isNull();

View File

@@ -3,14 +3,9 @@
#pragma once
#include <QColor>
#include <QGuiApplication>
#include <QPalette>
#include <QQmlEngine>
#include <QRegularExpression>
#include <Quotient/user.h>
class QmlUtils : public QObject
{
Q_OBJECT
@@ -30,31 +25,12 @@ public:
return _instance;
}
Q_INVOKABLE QVariantMap getUser(Quotient::User *user) const;
Q_INVOKABLE bool isValidJson(const QByteArray &json);
private:
QmlUtils() = default;
};
namespace Utils
{
/**
* @brief Get a color for a user from a hueF value.
*
* The lightness of the color will be modified depending on the current palette in
* order to maintain contrast.
*/
inline QColor getUserColor(qreal hueF)
{
const auto lightness = static_cast<QGuiApplication *>(QGuiApplication::instance())->palette().color(QPalette::Active, QPalette::Window).lightnessF();
// https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1);
}
}
namespace TextRegex
{
static const QRegularExpression endTagType{QStringLiteral("(>| )")};