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 ece56a55e8
commit 272f49876e
53 changed files with 332 additions and 604 deletions

View File

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

View File

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

View File

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

View File

@@ -51,6 +51,21 @@
"unsigned": { "unsigned": {
"age": 1234 "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() void EventHandlerTest::author()
{ {
auto event = room->messageEvents().at(0).get(); auto event = room->messageEvents().at(0).get();
auto author = room->user(event->senderId()); auto author = room->member(event->senderId());
EventHandler eventHandler(room, event); EventHandler eventHandler(room, event);
auto eventHandlerAuthor = eventHandler.getAuthor(); auto eventHandlerAuthor = eventHandler.getAuthor();
QCOMPARE(eventHandlerAuthor["isLocalUser"_ls], author->id() == room->localUser()->id()); QCOMPARE(eventHandlerAuthor.isLocalMember(), author.id() == room->localMember().id());
QCOMPARE(eventHandlerAuthor["id"_ls], author->id()); QCOMPARE(eventHandlerAuthor.id(), author.id());
QCOMPARE(eventHandlerAuthor["displayName"_ls], author->displayname(room)); QCOMPARE(eventHandlerAuthor.displayName(), author.displayName());
QCOMPARE(eventHandlerAuthor["avatarSource"_ls], room->avatarForMember(author)); QCOMPARE(eventHandlerAuthor.avatarUrl(), author.avatarUrl());
QCOMPARE(eventHandlerAuthor["avatarMediaId"_ls], author->avatarMediaId(room)); QCOMPARE(eventHandlerAuthor.avatarMediaId(), author.avatarMediaId());
QCOMPARE(eventHandlerAuthor["color"_ls], Utils::getUserColor(author->hueF())); QCOMPARE(eventHandlerAuthor.color(), author.color());
QCOMPARE(eventHandlerAuthor["object"_ls], QVariant::fromValue(author));
} }
void EventHandlerTest::nullAuthor() void EventHandlerTest::nullAuthor()
{ {
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getAuthor(), QVariantMap()); QCOMPARE(emptyHandler.getAuthor(), RoomMember());
EventHandler noEventHandler(room, nullptr); EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user."); 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() void EventHandlerTest::authorDisplayName()
@@ -393,31 +392,30 @@ void EventHandlerTest::nullReplyId()
void EventHandlerTest::replyAuthor() void EventHandlerTest::replyAuthor()
{ {
auto replyEvent = room->messageEvents().at(0).get(); 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()); EventHandler eventHandler(room, room->messageEvents().at(5).get());
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor(); auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
QCOMPARE(eventHandlerReplyAuthor["isLocalUser"_ls], replyAuthor->id() == room->localUser()->id()); QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id());
QCOMPARE(eventHandlerReplyAuthor["id"_ls], replyAuthor->id()); QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id());
QCOMPARE(eventHandlerReplyAuthor["displayName"_ls], replyAuthor->displayname(room)); QCOMPARE(eventHandlerReplyAuthor.displayName(), replyAuthor.displayName());
QCOMPARE(eventHandlerReplyAuthor["avatarSource"_ls], room->avatarForMember(replyAuthor)); QCOMPARE(eventHandlerReplyAuthor.avatarUrl(), replyAuthor.avatarUrl());
QCOMPARE(eventHandlerReplyAuthor["avatarMediaId"_ls], replyAuthor->avatarMediaId(room)); QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId());
QCOMPARE(eventHandlerReplyAuthor["color"_ls], Utils::getUserColor(replyAuthor->hueF())); QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color());
QCOMPARE(eventHandlerReplyAuthor["object"_ls], QVariant::fromValue(replyAuthor));
EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get()); EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), room->getUser(nullptr)); QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), RoomMember());
} }
void EventHandlerTest::nullReplyAuthor() void EventHandlerTest::nullReplyAuthor()
{ {
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr."); QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReplyAuthor(), QVariantMap()); QCOMPARE(emptyHandler.getReplyAuthor(), RoomMember());
EventHandler noEventHandler(room, nullptr); EventHandler noEventHandler(room, nullptr);
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user."); 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() void EventHandlerTest::replyBody()
@@ -531,10 +529,10 @@ void EventHandlerTest::readMarkers()
auto readMarkers = eventHandler.getReadMarkers(); auto readMarkers = eventHandler.getReadMarkers();
QCOMPARE(readMarkers.size(), 1); 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.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()); EventHandler eventHandler2(room, room->messageEvents().at(2).get());
QCOMPARE(eventHandler2.hasReadMarkers(), true); QCOMPARE(eventHandler2.hasReadMarkers(), true);
@@ -554,7 +552,7 @@ void EventHandlerTest::nullReadMarkers()
QCOMPARE(emptyHandler.hasReadMarkers(), false); QCOMPARE(emptyHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_room set to nullptr."); 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."); QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getNumberExcessReadMarkers(), QString()); QCOMPARE(emptyHandler.getNumberExcessReadMarkers(), QString());
@@ -568,7 +566,7 @@ void EventHandlerTest::nullReadMarkers()
QCOMPARE(noEventHandler.hasReadMarkers(), false); QCOMPARE(noEventHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_event set to nullptr."); 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."); QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getNumberExcessReadMarkers(), QString()); 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::ReactionRole), QStringLiteral("👍"));
QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole), QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole),
QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>")); 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::HasLocalMember), false);
QCOMPARE(model.data(model.index(0), ReactionModel::AuthorsRole), authorList);
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalUser), false);
} }
void ReactionModelTest::newReaction() 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++) { for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
if (const auto event = eventCast<const RoomMessageEvent>(&**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; QString originalString;
if (event->content()) { if (event->content()) {
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body; originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;

View File

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

View File

@@ -10,6 +10,12 @@
class ChatDocumentHandler; class ChatDocumentHandler;
namespace Quotient
{
class RoomMember;
}
/** /**
* @brief Defines a user mention in the current chat or edit text. * @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) 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 * Returns an empty RoomMember if not replying to a message.
* 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 user if not replying to a message. * @sa Quotient::RoomMember
*
* 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
*/ */
Q_PROPERTY(QVariantMap relationUser READ relationUser NOTIFY relationIdChanged) Q_PROPERTY(Quotient::RoomMember relationUser READ relationUser NOTIFY relationIdChanged)
/** /**
* @brief The content of the related message. * @brief The content of the related message.
@@ -161,7 +154,7 @@ public:
QString editId() const; QString editId() const;
void setEditId(const QString &editId); void setEditId(const QString &editId);
QVariantMap relationUser() const; Quotient::RoomMember relationUser() const;
QString relationMessage() const; QString relationMessage() const;

View File

@@ -19,6 +19,7 @@
#include <Quotient/events/simplestateevents.h> #include <Quotient/events/simplestateevents.h>
#include <Quotient/events/stickerevent.h> #include <Quotient/events/stickerevent.h>
#include <Quotient/quotient_common.h> #include <Quotient/quotient_common.h>
#include <Quotient/roommember.h>
#include "eventhandler_logging.h" #include "eventhandler_logging.h"
#include "events/locationbeaconevent.h" #include "events/locationbeaconevent.h"
@@ -61,20 +62,18 @@ MessageComponentType::Type EventHandler::messageComponentType() const
return MessageComponentType::typeForEvent(*m_event); return MessageComponentType::typeForEvent(*m_event);
} }
QVariantMap EventHandler::getAuthor(bool isPending) const Quotient::RoomMember EventHandler::getAuthor(bool isPending) const
{ {
if (m_room == nullptr) { if (m_room == nullptr) {
qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr."; qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr.";
return {}; return {};
} }
// If we have a room we can return an empty user by handing nullptr to m_room->getUser.
if (m_event == nullptr) { if (m_event == nullptr) {
qCWarning(EventHandling) << "getAuthor called with m_event set to nullptr. Returning empty user."; 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 isPending ? m_room->localMember() : m_room->member(m_event->senderId());
return m_room->getUser(author);
} }
QString EventHandler::getAuthorDisplayName(bool isPending) const QString EventHandler::getAuthorDisplayName(bool isPending) const
@@ -96,8 +95,8 @@ QString EventHandler::getAuthorDisplayName(bool isPending) const
} }
return previousDisplayName; return previousDisplayName;
} else { } else {
const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId()); const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
return m_room->htmlSafeMemberName(author->id()); return author.htmlSafeDisplayName();
} }
} }
@@ -112,8 +111,8 @@ QString EventHandler::singleLineAuthorDisplayname(bool isPending) const
return {}; return {};
} }
const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId()); const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
auto displayName = m_room->safeMemberName(author->id()); auto displayName = author.displayName();
displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" ")); displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br>"), QStringLiteral(" ")); displayName.replace(QStringLiteral("<br>"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br />\n"), QStringLiteral(" ")); displayName.replace(QStringLiteral("<br />\n"), QStringLiteral(" "));
@@ -220,7 +219,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; return true;
} }
@@ -318,7 +317,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
}, },
[this, prettyPrint](const RoomMemberEvent &e) { [this, prettyPrint](const RoomMemberEvent &e) {
// FIXME: Rewind to the name that was at the time of this event // 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.membership() == Membership::Leave) {
if (e.prevContent() && e.prevContent()->displayName) { if (e.prevContent() && e.prevContent()->displayName) {
subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped(); subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped();
@@ -326,7 +325,8 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
} }
if (prettyPrint) { 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 // The below code assumes senderName output in AuthorRole
@@ -800,25 +800,21 @@ MessageComponentType::Type EventHandler::replyMessageComponentType() const
return MessageComponentType::typeForEvent(*replyEvent); return MessageComponentType::typeForEvent(*replyEvent);
} }
QVariantMap EventHandler::getReplyAuthor() const Quotient::RoomMember EventHandler::getReplyAuthor() const
{ {
if (m_room == nullptr) { if (m_room == nullptr) {
qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr."; qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr.";
return {}; return {};
} }
// If we have a room we can return an empty user by handing nullptr to m_room->getUser.
if (m_event == nullptr) { if (m_event == nullptr) {
qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user."; 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 (auto replyPtr = m_room->getReplyForEvent(*m_event)) {
return m_room->member(replyPtr->senderId());
if (replyPtr) {
auto replyUser = m_room->user(replyPtr->senderId());
return m_room->getUser(replyUser);
} else { } else {
return m_room->getUser(nullptr); return m_room->member(QString());
} }
} }
@@ -966,11 +962,11 @@ bool EventHandler::hasReadMarkers() const
} }
auto userIds = m_room->userIdsAtEvent(m_event->id()); auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id()); userIds.remove(m_room->localMember().id());
return userIds.size() > 0; return userIds.size() > 0;
} }
QVariantList EventHandler::getReadMarkers(int maxMarkers) const QList<Quotient::RoomMember> EventHandler::getReadMarkers(int maxMarkers) const
{ {
if (m_room == nullptr) { if (m_room == nullptr) {
qCWarning(EventHandling) << "getReadMarkers called with m_room set to nullptr."; qCWarning(EventHandling) << "getReadMarkers called with m_room set to nullptr.";
@@ -982,18 +978,17 @@ QVariantList EventHandler::getReadMarkers(int maxMarkers) const
} }
auto userIds_temp = m_room->userIdsAtEvent(m_event->id()); 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(); auto userIds = userIds_temp.values();
if (userIds.count() > maxMarkers) { if (userIds.count() > maxMarkers) {
userIds = userIds.mid(0, maxMarkers); userIds = userIds.mid(0, maxMarkers);
} }
QVariantList users; QList<Quotient::RoomMember> users;
users.reserve(userIds.size()); users.reserve(userIds.size());
for (const auto &userId : userIds) { for (const auto &userId : userIds) {
auto user = m_room->user(userId); users += m_room->member(userId);
users += m_room->getUser(user);
} }
return users; return users;
@@ -1011,7 +1006,7 @@ QString EventHandler::getNumberExcessReadMarkers(int maxMarkers) const
} }
auto userIds = m_room->userIdsAtEvent(m_event->id()); auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id()); userIds.remove(m_room->localMember().id());
if (userIds.count() > maxMarkers) { if (userIds.count() > maxMarkers) {
return QStringLiteral("+ ") + QString::number(userIds.count() - maxMarkers); return QStringLiteral("+ ") + QString::number(userIds.count() - maxMarkers);
@@ -1032,7 +1027,7 @@ QString EventHandler::getReadMarkersString() const
} }
auto userIds = m_room->userIdsAtEvent(m_event->id()); 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 * The string ends up in the form
@@ -1040,10 +1035,12 @@ QString EventHandler::getReadMarkersString() const
*/ */
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size()); QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
for (const auto &userId : userIds) { for (const auto &userId : userIds) {
auto user = m_room->user(userId); auto member = m_room->member(userId);
auto displayName = user->displayname(m_room); QString displayName;
if (displayName.isEmpty()) { if (member.isEmpty()) {
displayName = userId; displayName = i18nc("A member who is not in the room has been requested.", "unknown member");
} else {
displayName = member.displayName();
} }
readMarkersString += displayName + i18nc("list separator", ", "); readMarkersString += displayName + i18nc("list separator", ", ");
} }

View File

@@ -13,6 +13,11 @@
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"
namespace Quotient
{
class RoomMember;
}
class LinkPreviewer; class LinkPreviewer;
class NeoChatRoom; class NeoChatRoom;
class ReactionModel; class ReactionModel;
@@ -51,30 +56,17 @@ public:
/** /**
* @brief Get the author of the event in context of the room. * @brief Get the author of the event in context of the room.
* *
* This is different to getting a Quotient::User object * An empty Quotient::RoomMember will be returned if the EventHandler hasn't had
* as neither of those can provide details like the displayName or avatarMediaId * the room or event initialised.
* 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.
* *
* @param isPending if the event is pending, i.e. has not been confirmed by * @param isPending if the event is pending, i.e. has not been confirmed by
* the server. * the server.
* *
* @return a QVariantMap for the user with the following properties: * @return a Quotient::RoomMember object for the user.
* - 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 * @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. * @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. * @brief Get the author of the event replied to in context of the room.
* *
* This is different to getting a Quotient::User object * An empty Quotient::RoomMember will be returned if the EventHandler hasn't had
* as neither of those can provide details like the displayName or avatarMediaId * the room or event initialised.
* 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 * @param isPending if the event is pending, i.e. has not been confirmed by
* intialised. An empty user (i.e. a QVariantMap with all the correct keys * the server.
* but empty values) will be returned if the room has been set but not an event.
* *
* @return a QVariantMap for the user with the following properties: * @return a Quotient::RoomMember object for the user.
* - 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 * @sa Quotient::RoomMember
*/ */
QVariantMap getReplyAuthor() const; Quotient::RoomMember getReplyAuthor() const;
/** /**
* @brief Output a string for the message content of the event replied to ready * @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 number of users shown plus the excess number will be
* the total number of other user read markers at an event. * 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. * @brief Returns the number of excess user read markers for the event.

View File

@@ -202,11 +202,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)); Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
return QString(); 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.")); Q_EMIT room->showMessage(NeoChatRoom::Positive, i18n("You are already in this room."));
return QString(); 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)); Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
return QString(); return QString();
} }
@@ -359,7 +359,7 @@ QList<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text)); Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
return QString(); 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)); Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
return QString(); return QString();
}, },
@@ -382,7 +382,7 @@ QList<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text)); Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
return QString(); 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)); Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
return QString(); return QString();
}, },
@@ -431,11 +431,11 @@ QList<ActionsModel::Action> actions{
if (!plEvent) { if (!plEvent) {
return QString(); 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.")); Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to ban users from this room."));
return QString(); 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( Q_EMIT room->showMessage(
NeoChatRoom::Error, 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])); 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 +464,7 @@ QList<ActionsModel::Action> actions{
if (!plEvent) { if (!plEvent) {
return QString(); 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.")); Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to unban users from this room."));
return QString(); return QString();
} }
@@ -495,7 +495,7 @@ QList<ActionsModel::Action> actions{
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0])); i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0]));
return QString(); 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.")); Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You cannot kick yourself from the room."));
return QString(); return QString();
} }
@@ -508,11 +508,11 @@ QList<ActionsModel::Action> actions{
return QString(); return QString();
} }
auto kick = plEvent->kick(); 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.")); Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to kick users from this room."));
return QString(); 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( Q_EMIT room->showMessage(
NeoChatRoom::Error, 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])); 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: case AssetRole:
return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString(); return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
case AuthorRole: case AuthorRole:
return m_room->getUser(data.senderId); return QVariant::fromValue(m_room->member(data.senderId));
case IsLiveRole: { case IsLiveRole: {
if (!data.beaconInfo["live"_ls].toBool()) { if (!data.beaconInfo["live"_ls].toBool()) {
return false; return false;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -5,13 +5,8 @@
#include "neochatroom.h" #include "neochatroom.h"
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/events/reactionevent.h> #include <Quotient/events/reactionevent.h>
#include <Quotient/roommember.h>
namespace Quotient
{
class User;
}
/** /**
* @class ReactionModel * @class ReactionModel
@@ -30,7 +25,7 @@ public:
*/ */
struct Reaction { struct Reaction {
QString reaction; /**< The reaction emoji. */ 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. */ TextContentRole = Qt::DisplayRole, /**< The text to show in the reaction. */
ReactionRole, /**< The reaction emoji. */ ReactionRole, /**< The reaction emoji. */
ToolTipRole, /**< The tool tip to show for the reaction. */ ToolTipRole, /**< The tool tip to show for the reaction. */
AuthorsRole, /**< The list of authors who sent the given reaction. */ HasLocalMember, /**< Whether the local member is in the list of authors. */
HasLocalUser, /**< Whether the local user is in the list of authors. */
}; };
explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room); 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: case ShowAuthorRole:
return true; return true;
case AuthorRole: case AuthorRole:
return eventHandler.getAuthor(); return QVariant::fromValue(eventHandler.getAuthor());
case ShowSectionRole: case ShowSectionRole:
if (row == 0) { if (row == 0) {
return true; return true;

View File

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

View File

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

View File

@@ -11,9 +11,9 @@
#include <Quotient/jobs/basejob.h> #include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h> #include <Quotient/quotient_common.h>
#include <Quotient/user.h>
#include <qcoro/qcorosignal.h> #include <qcoro/qcorosignal.h>
#include <Quotient/avatar.h>
#include <Quotient/connection.h> #include <Quotient/connection.h>
#include <Quotient/csapi/account-data.h> #include <Quotient/csapi/account-data.h>
#include <Quotient/csapi/directory.h> #include <Quotient/csapi/directory.h>
@@ -103,15 +103,15 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
if (this->joinState() != JoinState::Invite) { if (this->joinState() != JoinState::Invite) {
return; return;
} }
auto roomMemberEvent = currentState().get<RoomMemberEvent>(localUser()->id()); auto roomMemberEvent = currentState().get<RoomMemberEvent>(localMember().id());
QImage avatar_image; QImage avatar_image;
if (roomMemberEvent && !user(roomMemberEvent->senderId())->avatarUrl(this).isEmpty()) { if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
avatar_image = user(roomMemberEvent->senderId())->avatar(128, this); avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {});
} else { } else {
qWarning() << "using this room's avatar"; qWarning() << "using this room's avatar";
avatar_image = avatar(128); 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);
}); });
connect(this, &Room::changed, this, [this] { connect(this, &Room::changed, this, [this] {
Q_EMIT canEncryptRoomChanged(); Q_EMIT canEncryptRoomChanged();
@@ -259,18 +259,16 @@ void NeoChatRoom::forget()
QVariantList NeoChatRoom::getUsersTyping() const QVariantList NeoChatRoom::getUsersTyping() const
{ {
auto users = usersTyping(); auto members = membersTyping();
users.removeAll(localUser()); members.removeAll(localMember());
QVariantList userVariants; QVariantList userVariants;
for (const auto &user : users) { for (const auto &member : members) {
if (connection()->isIgnored(user->id())) { if (connection()->isIgnored(member.id())) {
continue; continue;
} }
userVariants.append(QVariantMap{ userVariants.append(QVariantMap{
{"id"_ls, user->id()}, {"id"_ls, member.id()},
{"avatarMediaId"_ls, user->avatarMediaId(this)}, {"displayName"_ls, member.displayName()},
{"displayName"_ls, user->displayname(this)},
{"display"_ls, user->name()},
}); });
} }
return userVariants; return userVariants;
@@ -278,7 +276,7 @@ QVariantList NeoChatRoom::getUsersTyping() const
void NeoChatRoom::sendTypingNotification(bool isTyping) 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 const RoomEvent *NeoChatRoom::lastEvent() const
@@ -316,7 +314,7 @@ const RoomEvent *NeoChatRoom::lastEvent() const
} }
} }
if (connection()->isIgnored(user(event->senderId()))) { if (connection()->isIgnored(event->senderId())) {
continue; continue;
} }
@@ -376,13 +374,13 @@ bool NeoChatRoom::isEventHighlighted(const RoomEvent *e) const
void NeoChatRoom::checkForHighlights(const Quotient::TimelineItem &ti) void NeoChatRoom::checkForHighlights(const Quotient::TimelineItem &ti)
{ {
auto localUserId = localUser()->id(); auto localMember = this->localMember();
if (ti->senderId() == localUserId) { if (ti->senderId() == localMember.id()) {
return; return;
} }
if (auto *e = ti.viewAs<RoomMessageEvent>()) { if (auto *e = ti.viewAs<RoomMessageEvent>()) {
const auto &text = e->plainBody(); 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); highlights.insert(e);
} }
} }
@@ -428,40 +426,6 @@ QDateTime NeoChatRoom::lastActiveTime()
return messageEvents().rbegin()->get()->originTimestamp(); 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 QString NeoChatRoom::avatarMediaId() const
{ {
if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) { if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) {
@@ -469,10 +433,10 @@ QString NeoChatRoom::avatarMediaId() const
} }
// Use the first (excluding self) user's avatar for direct chats // Use the first (excluding self) user's avatar for direct chats
const auto dcUsers = directChatUsers(); const auto directChatMembers = this->directChatMembers();
for (const auto u : dcUsers) { for (const auto member : directChatMembers) {
if (u != localUser()) { if (member != localMember()) {
return u->avatarMediaId(this); return member.avatarMediaId();
} }
} }
@@ -639,7 +603,7 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction
continue; continue;
} }
if (e->senderId() == localUser()->id()) { if (e->senderId() == localMember().id()) {
redactEventIds.push_back(e->id()); redactEventIds.push_back(e->id());
break; break;
} }
@@ -668,7 +632,7 @@ bool NeoChatRoom::canSendEvent(const QString &eventType) const
return false; return false;
} }
auto pl = plEvent->powerLevelForEvent(eventType); auto pl = plEvent->powerLevelForEvent(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id()); auto currentPl = plEvent->powerLevelForUser(localMember().id());
return currentPl >= pl; return currentPl >= pl;
} }
@@ -680,7 +644,7 @@ bool NeoChatRoom::canSendState(const QString &eventType) const
return false; return false;
} }
auto pl = plEvent->powerLevelForState(eventType); auto pl = plEvent->powerLevelForState(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id()); auto currentPl = plEvent->powerLevelForUser(localMember().id());
return currentPl >= pl; return currentPl >= pl;
} }
@@ -858,7 +822,7 @@ void NeoChatRoom::setUrlPreviewEnabled(const bool &urlPreviewEnabled)
* "type": "org.matrix.room.preview_urls", * "type": "org.matrix.room.preview_urls",
* } * }
*/ */
connection()->callApi<SetAccountDataPerRoomJob>(localUser()->id(), connection()->callApi<SetAccountDataPerRoomJob>(localMember().id(),
id(), id(),
"org.matrix.room.preview_urls"_ls, "org.matrix.room.preview_urls"_ls,
QJsonObject{{"disable"_ls, !urlPreviewEnabled}}); QJsonObject{{"disable"_ls, !urlPreviewEnabled}});
@@ -1526,7 +1490,7 @@ void NeoChatRoom::editLastMessage()
} }
// check if the current message's sender's id is same as the user's id // 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(); auto content = (*it)->contentJson();
if (e->msgtype() != MessageEventType::Unknown) { if (e->msgtype() != MessageEventType::Unknown) {
@@ -1651,13 +1615,9 @@ int NeoChatRoom::maxRoomVersion() const
return maxVersion; return maxVersion;
} }
Quotient::User *NeoChatRoom::directChatRemoteUser() const Quotient::RoomMember NeoChatRoom::directChatRemoteMember() const
{ {
auto users = connection()->directChatUsers(this); return directChatMembers()[0];
if (users.isEmpty()) {
return nullptr;
}
return users[0];
} }
void NeoChatRoom::sendLocation(float lat, float lon, const QString &description) void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)
@@ -1689,20 +1649,6 @@ QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType)
return QJsonDocument(accountData(eventType)->fullJson()).toJson(); 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) void NeoChatRoom::downloadEventFromServer(const QString &eventId)
{ {
if (findInTimeline(eventId) != historyEdge()) { if (findInTimeline(eventId) != historyEdge()) {
@@ -1774,10 +1720,9 @@ void NeoChatRoom::cleanupExtraEvent(const QString &eventId)
m_extraEvents.erase(it); m_extraEvents.erase(it);
} }
} }
QString NeoChatRoom::invitingUserId() const
User *NeoChatRoom::invitingUser() 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) void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, const QByteArray &content)

View File

@@ -10,7 +10,7 @@
#include <QQmlEngine> #include <QQmlEngine>
#include <QCoroTask> #include <QCoroTask>
#include <Quotient/user.h> #include <Quotient/roommember.h>
#include "enums/pushrule.h" #include "enums/pushrule.h"
#include "pollhandler.h" #include "pollhandler.h"
@@ -93,9 +93,9 @@ class NeoChatRoom : public Quotient::Room
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) 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. * @brief The Matrix IDs of this room's parents.
@@ -235,58 +235,6 @@ public:
explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {}); 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]] QVariantList getUsersTyping() const;
[[nodiscard]] QDateTime lastActiveTime(); [[nodiscard]] QDateTime lastActiveTime();
@@ -400,7 +348,7 @@ public:
[[nodiscard]] QString avatarMediaId() const; [[nodiscard]] QString avatarMediaId() const;
Quotient::User *directChatRemoteUser() const; Quotient::RoomMember directChatRemoteMember() const;
/** /**
* @brief Whether this room has one or more parent spaces set. * @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 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. * @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. * 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: private:
QSet<const Quotient::RoomEvent *> highlights; QSet<const Quotient::RoomEvent *> highlights;

View File

@@ -109,7 +109,7 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
auto room = connection->room(notification["room_id"_ls].toString()); auto room = connection->room(notification["room_id"_ls].toString());
if (shouldPostNotification(connection, n)) { if (shouldPostNotification(connection, n)) {
// The room might have been deleted (for example rejected invitation). // The room might have been deleted (for example rejected invitation).
auto sender = room->user(notification["event"_ls].toObject()["sender"_ls].toString()); auto sender = room->member(notification["event"_ls].toObject()["sender"_ls].toString());
QString body; QString body;
@@ -133,13 +133,13 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
} }
QImage avatar_image; QImage avatar_image;
if (!sender->avatarUrl(room).isEmpty()) { if (!sender.avatarUrl().isEmpty()) {
avatar_image = sender->avatar(128, room); avatar_image = room->memberAvatar(sender.id()).get(connection, 128, {});
} else { } else {
avatar_image = room->avatar(128); avatar_image = room->avatar(128);
} }
postNotification(dynamic_cast<NeoChatRoom *>(room), postNotification(dynamic_cast<NeoChatRoom *>(room),
sender->displayname(room), sender.displayName(),
body, body,
avatar_image, avatar_image,
notification["event"_ls].toObject()["event_id"_ls].toString(), notification["event"_ls].toObject()["event_id"_ls].toString(),
@@ -213,7 +213,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
if (!room) { if (!room) {
return; 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); Controller::instance().setActiveConnection(connection);
RoomManager::instance().setConnection(connection); RoomManager::instance().setConnection(connection);
RoomManager::instance().resolveResource(room->id()); RoomManager::instance().resolveResource(room->id());
@@ -230,7 +230,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
notification->setReplyAction(std::move(replyAction)); 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(); notification->sendEvent();
} }
@@ -276,7 +276,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QS
return; return;
} }
RoomManager::instance().leaveRoom(room); RoomManager::instance().leaveRoom(room);
room->connection()->addToIgnoredUsers(room->invitingUser()); room->connection()->addToIgnoredUsers(room->invitingUserId());
notification->close(); notification->close();
}); });
connect(notification, &KNotification::closed, this, [this, room]() { connect(notification, &KNotification::closed, this, [this, room]() {
@@ -286,7 +286,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QS
m_invitations.remove(room->id()); 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(); notification->sendEvent();
m_invitations.insert(room->id(), notification); m_invitations.insert(room->id(), notification);

View File

@@ -154,7 +154,7 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
return; return;
} }
QStringList ownAnswers; 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(); ownAnswers += answer.toString();
} }
if (ownAnswers.contains(answerId)) { if (ownAnswers.contains(answerId)) {
@@ -169,7 +169,7 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
} }
auto response = new PollResponseEvent(eventId, ownAnswers); 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); 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") text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard")
icon.name: "edit-copy" icon.name: "edit-copy"
onTriggered: if (room.isDirectChat()) { onTriggered: if (room.isDirectChat()) {
Clipboard.saveText(room.directChatRemoteUser.id); Clipboard.saveText(room.directChatRemoteMember.id);
} else if (room.canonicalAlias.length === 0) { } else if (room.canonicalAlias.length === 0) {
Clipboard.saveText(room.id); Clipboard.saveText(room.id);
} else { } else {

View File

@@ -40,18 +40,9 @@ Loader {
/** /**
* @brief The message author. * @brief The message author.
* *
* This should consist of the following: * A Quotient::RoomMember object.
* - 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.
* *
* @sa Quotient::User * @sa Quotient::RoomMember
*/ */
required property var author required property var author
@@ -90,7 +81,7 @@ Loader {
} }
component RemoveMessageAction: Kirigami.Action { component RemoveMessageAction: Kirigami.Action {
visible: author.isLocalUser || currentRoom.canSendState("redact") visible: author.isLocalMember || currentRoom.canSendState("redact")
text: i18n("Remove") text: i18n("Remove")
icon.name: "edit-delete-remove" icon.name: "edit-delete-remove"
icon.color: "red" icon.color: "red"
@@ -116,7 +107,7 @@ Loader {
component ReportMessageAction: Kirigami.Action { component ReportMessageAction: Kirigami.Action {
text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report") text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report")
icon.name: "dialog-warning-symbolic" icon.name: "dialog-warning-symbolic"
visible: !author.isLocalUser visible: !author.isLocalMember
onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReportSheet'), { onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReportSheet'), {
room: currentRoom, room: currentRoom,
eventId: eventId eventId: eventId

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -40,7 +40,7 @@ DelegateContextMenu {
currentRoom.editCache.editId = eventId; currentRoom.editCache.editId = eventId;
currentRoom.mainCache.replyId = ""; 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 {}, DelegateContextMenu.ReplyMessageAction {},
Kirigami.Action { Kirigami.Action {

View File

@@ -219,7 +219,7 @@ QQC2.ScrollView {
onClicked: { onClicked: {
userDelegate.highlighted = true; userDelegate.highlighted = true;
RoomManager.resolveResource(userDelegate.userId, "mention"); RoomManager.resolveResource(root.room.member(userDelegate.userId).uri)
} }
contentItem: RowLayout { contentItem: RowLayout {

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

View File

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

View File

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

View File

@@ -61,8 +61,8 @@ FormCard.FormCardPage {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
QQC2.Label { QQC2.Label {
id: powerLevelLabel id: powerLevelLabel
visible: !room.canSendState("m.room.power_levels") || (room.getUserPowerLevel(room.localUser.id) <= privilegedUserDelegate.powerLevel && privilegedUserDelegate.userId != room.localUser.id)
text: privilegedUserDelegate.powerLevelString 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 color: Kirigami.Theme.disabledTextColor
} }
QQC2.ComboBox { 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 e = eventCast<const Quotient::RoomMessageEvent>(event);
auto author = room->user(e->senderId()); auto author = room->member(e->senderId());
return QStringLiteral("* <a href=\"https://matrix.to/#/") + e->senderId() + QStringLiteral("\" style=\"color:") + Utils::getUserColor(author->hueF()).name() return QStringLiteral("* <a href=\"https://matrix.to/#/") + e->senderId() + QStringLiteral("\" style=\"color:") + author.color().name()
+ QStringLiteral("\">") + author->displayname(room) + QStringLiteral("</a> "); + QStringLiteral("\">") + author.htmlSafeDisplayName() + QStringLiteral("</a> ");
} }
QString TextHandler::convertCodeLanguageString(const QString &languageString) QString TextHandler::convertCodeLanguageString(const QString &languageString)

View File

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

View File

@@ -35,18 +35,9 @@ QQC2.Control {
/** /**
* @brief The message author. * @brief The message author.
* *
* This should consist of the following: * A Quotient::RoomMember object.
* - 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.
* *
* @sa Quotient::User * @sa Quotient::RoomMember
*/ */
property var author property var author
@@ -125,14 +116,14 @@ QQC2.Control {
id: nameButton id: nameButton
Layout.fillWidth: true Layout.fillWidth: true
contentItem: QQC2.Label { contentItem: QQC2.Label {
text: root.author.displayName text: root.author.disambiguatedName
color: root.author.color color: root.author.color
textFormat: Text.PlainText textFormat: Text.PlainText
font.weight: Font.Bold font.weight: Font.Bold
elide: Text.ElideRight elide: Text.ElideRight
} }
Accessible.name: contentItem.text Accessible.name: contentItem.text
onClicked: RoomManager.resolveResource(root.author.id, "mention") onClicked: RoomManager.resolveResource(root.author.uri)
} }
QQC2.Label { QQC2.Label {
id: timeLabel id: timeLabel
@@ -176,7 +167,7 @@ QQC2.Control {
visible: root.showBackground visible: root.showBackground
Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false 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); return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15);
} else if (root.showHighlight) { } else if (root.showHighlight) {
return Kirigami.Theme.positiveBackgroundColor; return Kirigami.Theme.positiveBackgroundColor;

View File

@@ -16,18 +16,9 @@ QQC2.Control {
/** /**
* @brief The message author. * @brief The message author.
* *
* This should consist of the following: * A Quotient::RoomMember object.
* - 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.
* *
* @sa Quotient::User * @sa Quotient::RoomMember
*/ */
required property var author required property var author

View File

@@ -19,18 +19,9 @@ ColumnLayout {
/** /**
* @brief The message author. * @brief The message author.
* *
* This should consist of the following: * A Quotient::RoomMember object.
* - 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.
* *
* @sa Quotient::User * @sa Quotient::RoomMember
*/ */
required property var author required property var author

View File

@@ -56,18 +56,9 @@ TimelineDelegate {
/** /**
* @brief The message author. * @brief The message author.
* *
* This should consist of the following: * A Quotient::RoomMember object.
* - 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.
* *
* @sa Quotient::User * @sa Quotient::RoomMember
*/ */
required property var author required property var author
@@ -281,11 +272,11 @@ TimelineDelegate {
visible: (root.showAuthor || root.alwaysShowAuthor) && Config.showAvatarInTimeline && (Config.compactLayout || !_private.showUserMessageOnRight) visible: (root.showAuthor || root.alwaysShowAuthor) && Config.showAvatarInTimeline && (Config.compactLayout || !_private.showUserMessageOnRight)
name: root.author.displayName name: root.author.displayName
source: root.author.avatarSource source: root.author.avatarUrl
color: root.author.color 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 { Bubble {
id: bubble id: bubble
@@ -411,7 +402,7 @@ TimelineDelegate {
/** /**
* @brief Whether local user messages should be aligned right. * @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() { function showMessageMenu() {
RoomManager.viewEventMenu(root.eventId, root.room, root.selectedText); RoomManager.viewEventMenu(root.eventId, root.room, root.selectedText);

View File

@@ -52,7 +52,7 @@ ColumnLayout {
delegate: RowLayout { delegate: RowLayout {
Layout.fillWidth: true Layout.fillWidth: true
CheckBox { 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"]) onClicked: root.pollHandler.sendPollAnswer(root.eventId, modelData["id"])
enabled: !root.pollHandler.hasEnded enabled: !root.pollHandler.hasEnded
} }

View File

@@ -35,7 +35,7 @@ Flow {
required property string textContent required property string textContent
required property string reaction required property string reaction
required property string toolTip required property string toolTip
required property bool hasLocalUser required property bool hasLocalMember
width: Math.max(contentItem.implicitWidth + leftPadding + rightPadding, height) width: Math.max(contentItem.implicitWidth + leftPadding + rightPadding, height)
height: Math.round(Kirigami.Units.gridUnit * 1.5) height: Math.round(Kirigami.Units.gridUnit * 1.5)
@@ -54,13 +54,13 @@ Flow {
padding: Kirigami.Units.smallSpacing padding: Kirigami.Units.smallSpacing
background: Kirigami.ShadowedRectangle { 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.inherit: false
Kirigami.Theme.colorSet: Config.compactLayout ? Kirigami.Theme.Window : Kirigami.Theme.View Kirigami.Theme.colorSet: Config.compactLayout ? Kirigami.Theme.Window : Kirigami.Theme.View
radius: height / 2 radius: height / 2
shadow { shadow {
size: Kirigami.Units.smallSpacing 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. * @brief The reply author.
* *
* This should consist of the following: * A Quotient::RoomMember object.
* - 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.
* *
* @sa Quotient::User * @sa Quotient::RoomMember
*/ */
required property var replyAuthor required property var replyAuthor
@@ -87,7 +78,7 @@ RowLayout {
implicitWidth: Kirigami.Units.iconSizes.small implicitWidth: Kirigami.Units.iconSizes.small
implicitHeight: Kirigami.Units.iconSizes.small implicitHeight: Kirigami.Units.iconSizes.small
source: root.replyAuthor.avatarSource source: root.replyAuthor.avatarUrl
name: root.replyAuthor.displayName name: root.replyAuthor.displayName
color: root.replyAuthor.color color: root.replyAuthor.color
} }

View File

@@ -24,18 +24,9 @@ RowLayout {
/** /**
* @brief The message author. * @brief The message author.
* *
* This should consist of the following: * A Quotient::RoomMember object.
* - 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.
* *
* @sa Quotient::User * @sa Quotient::RoomMember
*/ */
property var author: modelData.author property var author: modelData.author

View File

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

View File

@@ -7,45 +7,6 @@
#include <QJsonDocument> #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) bool QmlUtils::isValidJson(const QByteArray &json)
{ {
return !QJsonDocument::fromJson(json).isNull(); return !QJsonDocument::fromJson(json).isNull();

View File

@@ -3,14 +3,9 @@
#pragma once #pragma once
#include <QColor>
#include <QGuiApplication>
#include <QPalette>
#include <QQmlEngine> #include <QQmlEngine>
#include <QRegularExpression> #include <QRegularExpression>
#include <Quotient/user.h>
class QmlUtils : public QObject class QmlUtils : public QObject
{ {
Q_OBJECT Q_OBJECT
@@ -30,31 +25,12 @@ public:
return _instance; return _instance;
} }
Q_INVOKABLE QVariantMap getUser(Quotient::User *user) const;
Q_INVOKABLE bool isValidJson(const QByteArray &json); Q_INVOKABLE bool isValidJson(const QByteArray &json);
private: private:
QmlUtils() = default; 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 namespace TextRegex
{ {
static const QRegularExpression endTagType{QStringLiteral("(>| )")}; static const QRegularExpression endTagType{QStringLiteral("(>| )")};