diff --git a/autotests/chatbarcachetest.cpp b/autotests/chatbarcachetest.cpp index b34856879..3dfc50233 100644 --- a/autotests/chatbarcachetest.cpp +++ b/autotests/chatbarcachetest.cpp @@ -6,6 +6,7 @@ #include #include +#include #include #include @@ -50,7 +51,7 @@ void ChatBarCacheTest::empty() QCOMPARE(chatBarCache->replyId(), QString()); QCOMPARE(chatBarCache->isEditing(), false); QCOMPARE(chatBarCache->editId(), QString()); - QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr)); + QCOMPARE(chatBarCache->relationUser(), room->member(QString())); QCOMPARE(chatBarCache->relationMessage(), QString()); QCOMPARE(chatBarCache->attachmentPath(), QString()); } @@ -64,7 +65,7 @@ void ChatBarCacheTest::noRoom() // ChatBarCache has no parent. QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."); - QCOMPARE(chatBarCache->relationUser(), QVariantMap()); + QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember()); QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."); QCOMPARE(chatBarCache->relationMessage(), QString()); @@ -80,7 +81,7 @@ void ChatBarCacheTest::badParent() // ChatBarCache has no parent. QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation."); - QCOMPARE(chatBarCache->relationUser(), QVariantMap()); + QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember()); QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation."); QCOMPARE(chatBarCache->relationMessage(), QString()); @@ -98,7 +99,7 @@ void ChatBarCacheTest::reply() QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org")); QCOMPARE(chatBarCache->isEditing(), false); QCOMPARE(chatBarCache->editId(), QString()); - QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org")))); + QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org"))); QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message")); QCOMPARE(chatBarCache->attachmentPath(), QString()); } @@ -115,7 +116,7 @@ void ChatBarCacheTest::edit() QCOMPARE(chatBarCache->replyId(), QString()); QCOMPARE(chatBarCache->isEditing(), true); QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org")); - QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org")))); + QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org"))); QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message")); QCOMPARE(chatBarCache->attachmentPath(), QString()); } @@ -132,7 +133,7 @@ void ChatBarCacheTest::attachment() QCOMPARE(chatBarCache->replyId(), QString()); QCOMPARE(chatBarCache->isEditing(), false); QCOMPARE(chatBarCache->editId(), QString()); - QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr)); + QCOMPARE(chatBarCache->relationUser(), room->member(QString())); QCOMPARE(chatBarCache->relationMessage(), QString()); QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path")); } diff --git a/autotests/data/test-eventhandler-sync.json b/autotests/data/test-eventhandler-sync.json index f6f79bd0a..e25a56474 100644 --- a/autotests/data/test-eventhandler-sync.json +++ b/autotests/data/test-eventhandler-sync.json @@ -35,7 +35,7 @@ "content": { "$153456789:example.org": { "m.read": { - "@alice:matrix.org": { + "@alice:example.org": { "ts": 1436451550453 } } diff --git a/autotests/data/test-min-sync.json b/autotests/data/test-min-sync.json index 572651828..6b5637339 100644 --- a/autotests/data/test-min-sync.json +++ b/autotests/data/test-min-sync.json @@ -37,16 +37,14 @@ "events": [ { "content": { - "avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF", - "displayname": "Alice Margatroid", - "membership": "join", - "reason": "Looking for support" + "displayname": "Example", + "membership": "join" }, "event_id": "$143273582443PhrSn:example.org", "origin_server_ts": 1432735824653, "room_id": "!jEsUZKDJdhlrceRyVU:example.org", "sender": "@example:example.org", - "state_key": "@alice:example.org", + "state_key": "@example:example.org", "type": "m.room.member", "unsigned": { "age": 1234 diff --git a/autotests/data/test-texthandler-sync.json b/autotests/data/test-texthandler-sync.json index fa27df6c0..cba78ad26 100644 --- a/autotests/data/test-texthandler-sync.json +++ b/autotests/data/test-texthandler-sync.json @@ -51,6 +51,21 @@ "unsigned": { "age": 1234 } + }, + { + "content": { + "displayname": "Example", + "membership": "join" + }, + "event_id": "$143273582443PhrSn:example.org", + "origin_server_ts": 1432735824653, + "room_id": "!jEsUZKDJdhlrceRyVU:example.org", + "sender": "@example:example.org", + "state_key": "@example:example.org", + "type": "m.room.member", + "unsigned": { + "age": 1234 + } } ] }, diff --git a/autotests/eventhandlertest.cpp b/autotests/eventhandlertest.cpp index cb3dde831..09aa20aa3 100644 --- a/autotests/eventhandlertest.cpp +++ b/autotests/eventhandlertest.cpp @@ -101,28 +101,27 @@ void EventHandlerTest::nullEventId() void EventHandlerTest::author() { auto event = room->messageEvents().at(0).get(); - auto author = room->user(event->senderId()); + auto author = room->member(event->senderId()); EventHandler eventHandler(room, event); auto eventHandlerAuthor = eventHandler.getAuthor(); - QCOMPARE(eventHandlerAuthor["isLocalUser"_ls], author->id() == room->localUser()->id()); - QCOMPARE(eventHandlerAuthor["id"_ls], author->id()); - QCOMPARE(eventHandlerAuthor["displayName"_ls], author->displayname(room)); - QCOMPARE(eventHandlerAuthor["avatarSource"_ls], room->avatarForMember(author)); - QCOMPARE(eventHandlerAuthor["avatarMediaId"_ls], author->avatarMediaId(room)); - QCOMPARE(eventHandlerAuthor["color"_ls], Utils::getUserColor(author->hueF())); - QCOMPARE(eventHandlerAuthor["object"_ls], QVariant::fromValue(author)); + QCOMPARE(eventHandlerAuthor.isLocalMember(), author.id() == room->localMember().id()); + QCOMPARE(eventHandlerAuthor.id(), author.id()); + QCOMPARE(eventHandlerAuthor.displayName(), author.displayName()); + QCOMPARE(eventHandlerAuthor.avatarUrl(), author.avatarUrl()); + QCOMPARE(eventHandlerAuthor.avatarMediaId(), author.avatarMediaId()); + QCOMPARE(eventHandlerAuthor.color(), author.color()); } void EventHandlerTest::nullAuthor() { QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr."); - QCOMPARE(emptyHandler.getAuthor(), QVariantMap()); + QCOMPARE(emptyHandler.getAuthor(), RoomMember()); EventHandler noEventHandler(room, nullptr); QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user."); - QCOMPARE(noEventHandler.getAuthor(), room->getUser(nullptr)); + QCOMPARE(noEventHandler.getAuthor(), RoomMember()); } void EventHandlerTest::authorDisplayName() @@ -393,31 +392,30 @@ void EventHandlerTest::nullReplyId() void EventHandlerTest::replyAuthor() { auto replyEvent = room->messageEvents().at(0).get(); - auto replyAuthor = room->user(replyEvent->senderId()); + auto replyAuthor = room->member(replyEvent->senderId()); EventHandler eventHandler(room, room->messageEvents().at(5).get()); auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor(); - QCOMPARE(eventHandlerReplyAuthor["isLocalUser"_ls], replyAuthor->id() == room->localUser()->id()); - QCOMPARE(eventHandlerReplyAuthor["id"_ls], replyAuthor->id()); - QCOMPARE(eventHandlerReplyAuthor["displayName"_ls], replyAuthor->displayname(room)); - QCOMPARE(eventHandlerReplyAuthor["avatarSource"_ls], room->avatarForMember(replyAuthor)); - QCOMPARE(eventHandlerReplyAuthor["avatarMediaId"_ls], replyAuthor->avatarMediaId(room)); - QCOMPARE(eventHandlerReplyAuthor["color"_ls], Utils::getUserColor(replyAuthor->hueF())); - QCOMPARE(eventHandlerReplyAuthor["object"_ls], QVariant::fromValue(replyAuthor)); + QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id()); + QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id()); + QCOMPARE(eventHandlerReplyAuthor.displayName(), replyAuthor.displayName()); + QCOMPARE(eventHandlerReplyAuthor.avatarUrl(), replyAuthor.avatarUrl()); + QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId()); + QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color()); EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get()); - QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), room->getUser(nullptr)); + QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), RoomMember()); } void EventHandlerTest::nullReplyAuthor() { QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr."); - QCOMPARE(emptyHandler.getReplyAuthor(), QVariantMap()); + QCOMPARE(emptyHandler.getReplyAuthor(), RoomMember()); EventHandler noEventHandler(room, nullptr); QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user."); - QCOMPARE(noEventHandler.getReplyAuthor(), room->getUser(nullptr)); + QCOMPARE(noEventHandler.getReplyAuthor(), RoomMember()); } void EventHandlerTest::replyBody() @@ -531,10 +529,10 @@ void EventHandlerTest::readMarkers() auto readMarkers = eventHandler.getReadMarkers(); QCOMPARE(readMarkers.size(), 1); - QCOMPARE(readMarkers[0].toMap()["id"_ls], QStringLiteral("@alice:matrix.org")); + QCOMPARE(readMarkers[0].id(), QStringLiteral("@alice:example.org")); QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QString()); - QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: @alice:matrix.org")); + QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: Alice Margatroid")); EventHandler eventHandler2(room, room->messageEvents().at(2).get()); QCOMPARE(eventHandler2.hasReadMarkers(), true); @@ -554,7 +552,7 @@ void EventHandlerTest::nullReadMarkers() QCOMPARE(emptyHandler.hasReadMarkers(), false); QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_room set to nullptr."); - QCOMPARE(emptyHandler.getReadMarkers(), QVariantList()); + QCOMPARE(emptyHandler.getReadMarkers(), QList()); QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_room set to nullptr."); QCOMPARE(emptyHandler.getNumberExcessReadMarkers(), QString()); @@ -568,7 +566,7 @@ void EventHandlerTest::nullReadMarkers() QCOMPARE(noEventHandler.hasReadMarkers(), false); QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_event set to nullptr."); - QCOMPARE(noEventHandler.getReadMarkers(), QVariantList()); + QCOMPARE(noEventHandler.getReadMarkers(), QList()); QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_event set to nullptr."); QCOMPARE(noEventHandler.getNumberExcessReadMarkers(), QString()); diff --git a/autotests/reactionmodeltest.cpp b/autotests/reactionmodeltest.cpp index e807ae783..d23bde17f 100644 --- a/autotests/reactionmodeltest.cpp +++ b/autotests/reactionmodeltest.cpp @@ -53,9 +53,7 @@ void ReactionModelTest::basicReaction() QCOMPARE(model.data(model.index(0), ReactionModel::ReactionRole), QStringLiteral("👍")); QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole), QStringLiteral("@alice:matrix.org reacted with 👍")); - auto authorList = QVariantList{room->getUser(room->user(QStringLiteral("@alice:matrix.org")))}; - QCOMPARE(model.data(model.index(0), ReactionModel::AuthorsRole), authorList); - QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalUser), false); + QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalMember), false); } void ReactionModelTest::newReaction() diff --git a/src/actionshandler.cpp b/src/actionshandler.cpp index 8fd3cf535..e5ddaadbd 100644 --- a/src/actionshandler.cpp +++ b/src/actionshandler.cpp @@ -91,7 +91,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) { if (const auto event = eventCast(&**it)) { - if (event->senderId() == m_room->localUser()->id() && event->hasTextContent()) { + if (event->senderId() == m_room->localMember().id() && event->hasTextContent()) { QString originalString; if (event->content()) { originalString = static_cast(event->content())->body; diff --git a/src/chatbarcache.cpp b/src/chatbarcache.cpp index d123db5f3..95fbd38f9 100644 --- a/src/chatbarcache.cpp +++ b/src/chatbarcache.cpp @@ -3,6 +3,8 @@ #include "chatbarcache.h" +#include + #include "chatdocumenthandler.h" #include "eventhandler.h" #include "neochatroom.h" @@ -84,7 +86,7 @@ void ChatBarCache::setEditId(const QString &editId) Q_EMIT attachmentPathChanged(); } -QVariantMap ChatBarCache::relationUser() const +Quotient::RoomMember ChatBarCache::relationUser() const { if (parent() == nullptr) { qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation."; @@ -96,9 +98,9 @@ QVariantMap ChatBarCache::relationUser() const return {}; } if (m_relationId.isEmpty()) { - return room->getUser(nullptr); + return room->member(QString()); } - return room->getUser(room->user((*room->findInTimeline(m_relationId))->senderId())); + return room->member((*room->findInTimeline(m_relationId))->senderId()); } QString ChatBarCache::relationMessage() const diff --git a/src/chatbarcache.h b/src/chatbarcache.h index 3dbbe8435..3d3ff0777 100644 --- a/src/chatbarcache.h +++ b/src/chatbarcache.h @@ -10,6 +10,12 @@ class ChatDocumentHandler; +namespace Quotient +{ +class RoomMember; +} + + /** * @brief Defines a user mention in the current chat or edit text. */ @@ -88,26 +94,13 @@ class ChatBarCache : public QObject Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged) /** - * @brief Get the user for the message being replied to. + * @brief Get the RoomMember object for the message being replied to. * - * This is different to getting a Quotient::User object - * as neither of those can provide details like the displayName or avatarMediaId - * without the room context as these can vary from room to room. + * Returns an empty RoomMember if not replying to a message. * - * Returns an empty user if not replying to a message. - * - * The user QVariantMap has the following properties: - * - isLocalUser - Whether the user is the local user. - * - id - The matrix ID of the user. - * - displayName - Display name in the context of this room. - * - avatarSource - The mxc URL for the user's avatar in the current room. - * - avatarMediaId - Avatar id in the context of this room. - * - color - Color for the user. - * - object - The Quotient::User object for the user. - * - * @sa getUser, Quotient::User + * @sa Quotient::RoomMember */ - Q_PROPERTY(QVariantMap relationUser READ relationUser NOTIFY relationIdChanged) + Q_PROPERTY(Quotient::RoomMember relationUser READ relationUser NOTIFY relationIdChanged) /** * @brief The content of the related message. @@ -161,7 +154,7 @@ public: QString editId() const; void setEditId(const QString &editId); - QVariantMap relationUser() const; + Quotient::RoomMember relationUser() const; QString relationMessage() const; diff --git a/src/eventhandler.cpp b/src/eventhandler.cpp index 4d644dea8..3ad95ab76 100644 --- a/src/eventhandler.cpp +++ b/src/eventhandler.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include "eventhandler_logging.h" #include "events/locationbeaconevent.h" @@ -60,20 +61,18 @@ MessageComponentType::Type EventHandler::messageComponentType() const return MessageComponentType::typeForEvent(*m_event); } -QVariantMap EventHandler::getAuthor(bool isPending) const +Quotient::RoomMember EventHandler::getAuthor(bool isPending) const { if (m_room == nullptr) { qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr."; return {}; } - // If we have a room we can return an empty user by handing nullptr to m_room->getUser. if (m_event == nullptr) { qCWarning(EventHandling) << "getAuthor called with m_event set to nullptr. Returning empty user."; - return m_room->getUser(nullptr); + return {}; } - const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId()); - return m_room->getUser(author); + return isPending ? m_room->localMember() : m_room->member(m_event->senderId()); } QString EventHandler::getAuthorDisplayName(bool isPending) const @@ -95,8 +94,8 @@ QString EventHandler::getAuthorDisplayName(bool isPending) const } return previousDisplayName; } else { - const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId()); - return m_room->htmlSafeMemberName(author->id()); + const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId()); + return author.htmlSafeDisplayName(); } } @@ -111,8 +110,8 @@ QString EventHandler::singleLineAuthorDisplayname(bool isPending) const return {}; } - const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId()); - auto displayName = m_room->safeMemberName(author->id()); + const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId()); + auto displayName = author.displayName(); displayName.replace(QStringLiteral("
\n"), QStringLiteral(" ")); displayName.replace(QStringLiteral("
"), QStringLiteral(" ")); displayName.replace(QStringLiteral("
\n"), QStringLiteral(" ")); @@ -219,7 +218,7 @@ bool EventHandler::isHidden() } } - if (m_room->connection()->isIgnored(m_room->user(m_event->senderId()))) { + if (m_room->connection()->isIgnored(m_event->senderId())) { return true; } @@ -317,7 +316,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f }, [this, prettyPrint](const RoomMemberEvent &e) { // FIXME: Rewind to the name that was at the time of this event - auto subjectName = m_room->htmlSafeMemberName(e.userId()); + auto subjectName = m_room->member(e.userId()).htmlSafeDisplayName(); if (e.membership() == Membership::Leave) { if (e.prevContent() && e.prevContent()->displayName) { subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped(); @@ -325,7 +324,8 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f } if (prettyPrint) { - subjectName = QStringLiteral("%2").arg(e.userId(), subjectName); + subjectName = QStringLiteral("%3") + .arg(e.userId(), m_room->member(e.userId()).color().name(), subjectName); } // The below code assumes senderName output in AuthorRole @@ -799,25 +799,21 @@ MessageComponentType::Type EventHandler::replyMessageComponentType() const return MessageComponentType::typeForEvent(*replyEvent); } -QVariantMap EventHandler::getReplyAuthor() const +Quotient::RoomMember EventHandler::getReplyAuthor() const { if (m_room == nullptr) { qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr."; return {}; } - // If we have a room we can return an empty user by handing nullptr to m_room->getUser. if (m_event == nullptr) { qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user."; - return m_room->getUser(nullptr); + return {}; } - auto replyPtr = m_room->getReplyForEvent(*m_event); - - if (replyPtr) { - auto replyUser = m_room->user(replyPtr->senderId()); - return m_room->getUser(replyUser); + if (auto replyPtr = m_room->getReplyForEvent(*m_event)) { + return m_room->member(replyPtr->senderId()); } else { - return m_room->getUser(nullptr); + return m_room->member(QString()); } } @@ -965,11 +961,11 @@ bool EventHandler::hasReadMarkers() const } auto userIds = m_room->userIdsAtEvent(m_event->id()); - userIds.remove(m_room->localUser()->id()); + userIds.remove(m_room->localMember().id()); return userIds.size() > 0; } -QVariantList EventHandler::getReadMarkers(int maxMarkers) const +QList EventHandler::getReadMarkers(int maxMarkers) const { if (m_room == nullptr) { qCWarning(EventHandling) << "getReadMarkers called with m_room set to nullptr."; @@ -981,18 +977,17 @@ QVariantList EventHandler::getReadMarkers(int maxMarkers) const } auto userIds_temp = m_room->userIdsAtEvent(m_event->id()); - userIds_temp.remove(m_room->localUser()->id()); + userIds_temp.remove(m_room->localMember().id()); auto userIds = userIds_temp.values(); if (userIds.count() > maxMarkers) { userIds = userIds.mid(0, maxMarkers); } - QVariantList users; + QList users; users.reserve(userIds.size()); for (const auto &userId : userIds) { - auto user = m_room->user(userId); - users += m_room->getUser(user); + users += m_room->member(userId); } return users; @@ -1010,7 +1005,7 @@ QString EventHandler::getNumberExcessReadMarkers(int maxMarkers) const } auto userIds = m_room->userIdsAtEvent(m_event->id()); - userIds.remove(m_room->localUser()->id()); + userIds.remove(m_room->localMember().id()); if (userIds.count() > maxMarkers) { return QStringLiteral("+ ") + QString::number(userIds.count() - maxMarkers); @@ -1031,7 +1026,7 @@ QString EventHandler::getReadMarkersString() const } auto userIds = m_room->userIdsAtEvent(m_event->id()); - userIds.remove(m_room->localUser()->id()); + userIds.remove(m_room->localMember().id()); /** * The string ends up in the form @@ -1039,10 +1034,12 @@ QString EventHandler::getReadMarkersString() const */ QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size()); for (const auto &userId : userIds) { - auto user = m_room->user(userId); - auto displayName = user->displayname(m_room); - if (displayName.isEmpty()) { - displayName = userId; + auto member = m_room->member(userId); + QString displayName; + if (member.isEmpty()) { + displayName = i18nc("A member who is not in the room has been requested.", "unknown member"); + } else { + displayName = member.displayName(); } readMarkersString += displayName + i18nc("list separator", ", "); } diff --git a/src/eventhandler.h b/src/eventhandler.h index 68bbafc9a..e0d7a160b 100644 --- a/src/eventhandler.h +++ b/src/eventhandler.h @@ -13,6 +13,11 @@ #include "enums/messagecomponenttype.h" +namespace Quotient +{ +class RoomMember; +} + class LinkPreviewer; class NeoChatRoom; class ReactionModel; @@ -51,30 +56,17 @@ public: /** * @brief Get the author of the event in context of the room. * - * This is different to getting a Quotient::User object - * as neither of those can provide details like the displayName or avatarMediaId - * without the room context as these can vary from room to room. This function - * uses the room context and outputs the result as QVariantMap. - * - * An empty QVariantMap will be returned if the EventHandler hasn't had the room - * intialised. An empty user (i.e. a QVariantMap with all the correct keys - * but empty values) will be returned if the room has been set but not an event. + * An empty Quotient::RoomMember will be returned if the EventHandler hasn't had + * the room or event initialised. * * @param isPending if the event is pending, i.e. has not been confirmed by * the server. * - * @return a QVariantMap for the user with the following properties: - * - isLocalUser - Whether the user is the local user. - * - id - The matrix ID of the user. - * - displayName - Display name in the context of this room. - * - avatarSource - The mxc URL for the user's avatar in the current room. - * - avatarMediaId - Avatar id in the context of this room. - * - color - Color for the user. - * - object - The Quotient::User object for the user. + * @return a Quotient::RoomMember object for the user. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ - QVariantMap getAuthor(bool isPending = false) const; + Quotient::RoomMember getAuthor(bool isPending = false) const; /** * @brief Get the display name of the event author. @@ -251,27 +243,17 @@ public: /** * @brief Get the author of the event replied to in context of the room. * - * This is different to getting a Quotient::User object - * as neither of those can provide details like the displayName or avatarMediaId - * without the room context as these can vary from room to room. This function - * uses the room context and outputs the result as QVariantMap. + * An empty Quotient::RoomMember will be returned if the EventHandler hasn't had + * the room or event initialised. * - * An empty QVariantMap will be returned if the EventHandler hasn't had the room - * intialised. An empty user (i.e. a QVariantMap with all the correct keys - * but empty values) will be returned if the room has been set but not an event. + * @param isPending if the event is pending, i.e. has not been confirmed by + * the server. * - * @return a QVariantMap for the user with the following properties: - * - isLocalUser - Whether the user is the local user. - * - id - The matrix ID of the user. - * - displayName - Display name in the context of this room. - * - avatarSource - The mxc URL for the user's avatar in the current room. - * - avatarMediaId - Avatar id in the context of this room. - * - color - Color for the user. - * - object - The Quotient::User object for the user. + * @return a Quotient::RoomMember object for the user. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ - QVariantMap getReplyAuthor() const; + Quotient::RoomMember getReplyAuthor() const; /** * @brief Output a string for the message content of the event replied to ready @@ -375,7 +357,7 @@ public: * the number of users shown plus the excess number will be * the total number of other user read markers at an event. */ - QVariantList getReadMarkers(int maxMarkers = 5) const; + QList getReadMarkers(int maxMarkers = 5) const; /** * @brief Returns the number of excess user read markers for the event. diff --git a/src/models/actionsmodel.cpp b/src/models/actionsmodel.cpp index e0d66df72..a4623cf72 100644 --- a/src/models/actionsmodel.cpp +++ b/src/models/actionsmodel.cpp @@ -9,6 +9,7 @@ #include "roommanager.h" #include #include +#include #include @@ -202,11 +203,11 @@ QList actions{ Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc(" is banned from this room.", "%1 is banned from this room.", text)); return QString(); } - if (room->localUser()->id() == text) { + if (room->localMember().id() == text) { Q_EMIT room->showMessage(NeoChatRoom::Positive, i18n("You are already in this room.")); return QString(); } - if (room->users().contains(room->user(text))) { + if (room->members().contains(room->member(text))) { Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc(" is already in this room.", "%1 is already in this room.", text)); return QString(); } @@ -359,7 +360,7 @@ QList actions{ Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc(" is already ignored.", "%1 is already ignored.", text)); return QString(); } - room->connection()->addToIgnoredUsers(room->connection()->user(text)); + room->connection()->addToIgnoredUsers(text); Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc(" is now ignored", "%1 is now ignored.", text)); return QString(); }, @@ -382,7 +383,7 @@ QList actions{ Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc(" is not ignored.", "%1 is not ignored.", text)); return QString(); } - room->connection()->removeFromIgnoredUsers(room->connection()->user(text)); + room->connection()->removeFromIgnoredUsers(text); Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc(" is no longer ignored.", "%1 is no longer ignored.", text)); return QString(); }, @@ -431,11 +432,11 @@ QList actions{ if (!plEvent) { return QString(); } - if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) { + if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) { Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to ban users from this room.")); return QString(); } - if (plEvent->powerLevelForUser(room->localUser()->id()) <= plEvent->powerLevelForUser(parts[0])) { + if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) { Q_EMIT room->showMessage( NeoChatRoom::Error, i18nc("You are not allowed to ban from this room.", "You are not allowed to ban %1 from this room.", parts[0])); @@ -464,7 +465,7 @@ QList actions{ if (!plEvent) { return QString(); } - if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) { + if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) { Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to unban users from this room.")); return QString(); } @@ -495,7 +496,7 @@ QList actions{ i18nc("'' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0])); return QString(); } - if (parts[0] == room->localUser()->id()) { + if (parts[0] == room->localMember().id()) { Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You cannot kick yourself from the room.")); return QString(); } @@ -508,11 +509,11 @@ QList actions{ return QString(); } auto kick = plEvent->kick(); - if (plEvent->powerLevelForUser(room->localUser()->id()) < kick) { + if (plEvent->powerLevelForUser(room->localMember().id()) < kick) { Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to kick users from this room.")); return QString(); } - if (plEvent->powerLevelForUser(room->localUser()->id()) <= plEvent->powerLevelForUser(parts[0])) { + if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) { Q_EMIT room->showMessage( NeoChatRoom::Error, i18nc("You are not allowed to kick from this room", "You are not allowed to kick %1 from this room.", parts[0])); diff --git a/src/models/livelocationsmodel.cpp b/src/models/livelocationsmodel.cpp index 85656078b..80894e547 100644 --- a/src/models/livelocationsmodel.cpp +++ b/src/models/livelocationsmodel.cpp @@ -80,7 +80,7 @@ QVariant LiveLocationsModel::data(const QModelIndex &index, int roleName) const case AssetRole: return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString(); case AuthorRole: - return m_room->getUser(data.senderId); + return QVariant::fromValue(m_room->member(data.senderId)); case IsLiveRole: { if (!data.beaconInfo["live"_ls].toBool()) { return false; diff --git a/src/models/locationsmodel.cpp b/src/models/locationsmodel.cpp index 1f15f12bd..0c490f16e 100644 --- a/src/models/locationsmodel.cpp +++ b/src/models/locationsmodel.cpp @@ -63,7 +63,7 @@ void LocationsModel::addLocation(const RoomMessageEvent *event) .latitude = latitude, .longitude = longitude, .content = event->contentJson(), - .author = m_room->user(event->senderId()), + .member = m_room->member(event->senderId()), }; endInsertRows(); } @@ -105,7 +105,7 @@ QVariant LocationsModel::data(const QModelIndex &index, int roleName) const } else if (roleName == AssetRole) { return m_locations[row].content["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString(); } else if (roleName == AuthorRole) { - return m_room->getUser(m_locations[row].author); + return QVariant::fromValue(m_locations[row].member); } return {}; } diff --git a/src/models/locationsmodel.h b/src/models/locationsmodel.h index 7f7b1fb97..4b4992851 100644 --- a/src/models/locationsmodel.h +++ b/src/models/locationsmodel.h @@ -11,7 +11,7 @@ #include "neochatroom.h" #include -#include +#include class LocationsModel : public QAbstractListModel { @@ -57,7 +57,7 @@ private: float latitude; float longitude; QJsonObject content; - Quotient::User *author; + Quotient::RoomMember member; }; QList m_locations; void addLocation(const Quotient::RoomMessageEvent *event); diff --git a/src/models/messagecontentmodel.cpp b/src/models/messagecontentmodel.cpp index 41707e7bb..c079f5e2d 100644 --- a/src/models/messagecontentmodel.cpp +++ b/src/models/messagecontentmodel.cpp @@ -194,7 +194,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const return eventHandler.getId(); } if (role == AuthorRole) { - return eventHandler.getAuthor(false); + return QVariant::fromValue(eventHandler.getAuthor(false)); } if (role == MediaInfoRole) { return eventHandler.getMediaInfo(); @@ -224,7 +224,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const return eventHandler.getReplyId(); } if (role == ReplyAuthorRole) { - return eventHandler.getReplyAuthor(); + return QVariant::fromValue(eventHandler.getReplyAuthor()); } if (role == ReplyContentModelRole) { return QVariant::fromValue(m_replyModel); diff --git a/src/models/messageeventmodel.cpp b/src/models/messageeventmodel.cpp index 77cc4ce69..478bca2ca 100644 --- a/src/models/messageeventmodel.cpp +++ b/src/models/messageeventmodel.cpp @@ -11,7 +11,7 @@ #include #include #include -#include +#include #include #include @@ -222,7 +222,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room) beginResetModel(); endResetModel(); }); - qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localUser()->id(); + qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localMember().id(); } else { lastReadEventId.clear(); } @@ -460,7 +460,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const } if (role == AuthorRole) { - return eventHandler.getAuthor(isPending); + return QVariant::fromValue(eventHandler.getAuthor(isPending)); } if (role == HighlightRole) { @@ -539,7 +539,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const } if (role == ReadMarkersRole) { - return eventHandler.getReadMarkers(); + return QVariant::fromValue(eventHandler.getReadMarkers()); } if (role == ExcessReadMarkersRole) { @@ -592,7 +592,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const } if (role == IsEditableRole) { - return eventHandler.messageComponentType() == MessageComponentType::Text && evt.senderId() == m_currentRoom->localUser()->id(); + return eventHandler.messageComponentType() == MessageComponentType::Text && evt.senderId() == m_currentRoom->localMember().id(); } return {}; diff --git a/src/models/notificationsmodel.cpp b/src/models/notificationsmodel.cpp index 9cd573d3c..b3bfed5f6 100644 --- a/src/models/notificationsmodel.cpp +++ b/src/models/notificationsmodel.cpp @@ -116,7 +116,7 @@ void NotificationsModel::loadData() if (!room) { continue; } - auto u = room->memberAvatarUrl(authorId); + auto u = room->member(authorId).avatarUrl(); auto avatar = u.isEmpty() ? QUrl() : connection()->makeMediaUrl(u); const auto &authorAvatar = avatar.isValid() && avatar.scheme() == QStringLiteral("mxc") ? avatar : QUrl(); @@ -125,9 +125,9 @@ void NotificationsModel::loadData() beginInsertRows({}, m_notifications.length(), m_notifications.length()); m_notifications += Notification{ .roomId = notification.roomId, - .text = room->htmlSafeMemberName(authorId) + (roomEvent->is() ? QStringLiteral(" ") : QStringLiteral(": ")) + .text = room->member(authorId).htmlSafeDisplayName() + (roomEvent->is() ? QStringLiteral(" ") : QStringLiteral(": ")) + eventHandler.getPlainBody(true), - .authorName = room->htmlSafeMemberName(authorId), + .authorName = room->member(authorId).htmlSafeDisplayName(), .authorAvatar = authorAvatar, .eventId = roomEvent->id(), .roomDisplayName = room->displayName(), diff --git a/src/models/reactionmodel.cpp b/src/models/reactionmodel.cpp index d2793eecb..b56142497 100644 --- a/src/models/reactionmodel.cpp +++ b/src/models/reactionmodel.cpp @@ -15,7 +15,7 @@ #include -#include +#include ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room) : QAbstractListModel(nullptr) @@ -69,8 +69,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const text += i18nc("Separate the usernames of users", " and "); } } - auto displayName = reaction.authors.at(i).toMap()[QStringLiteral("displayName")].toString(); - text += displayName.isEmpty() ? reaction.authors.at(i).toMap()[QStringLiteral("id")].toString() : displayName; + text += reaction.authors.at(i).displayName(); } if (reaction.authors.count() > 3) { @@ -86,13 +85,9 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const return text; } - if (role == AuthorsRole) { - return reaction.authors; - } - - if (role == HasLocalUser) { + if (role == HasLocalMember) { for (auto author : reaction.authors) { - if (author.toMap()[QStringLiteral("id")] == m_room->localUser()->id()) { + if (author.id() == m_room->localMember().id()) { return true; } } @@ -121,13 +116,13 @@ void ReactionModel::updateReactions() return; }; - QMap> reactions = {}; + QMap> reactions = {}; for (const auto &a : annotations) { if (a->isRedacted()) { // Just in case? continue; } if (const auto &e = eventCast(a)) { - reactions[e->key()].append(m_room->user(e->senderId())); + reactions[e->key()].append(m_room->member(e->senderId())); if (e->contentJson()[QStringLiteral("shortcode")].toString().length()) { m_shortcodes[e->key()] = e->contentJson()[QStringLiteral("shortcode")].toString().toHtmlEscaped(); } @@ -138,15 +133,14 @@ void ReactionModel::updateReactions() endResetModel(); return; } - auto i = reactions.constBegin(); while (i != reactions.constEnd()) { - QVariantList authors; - for (const auto &author : i.value()) { - authors.append(m_room->getUser(author)); + QList members; + for (const auto &member : i.value()) { + members.append(member); } - m_reactions.append(ReactionModel::Reaction{i.key(), authors}); + m_reactions.append(ReactionModel::Reaction{i.key(), members}); ++i; } @@ -159,8 +153,7 @@ QHash ReactionModel::roleNames() const {TextContentRole, "textContent"}, {ReactionRole, "reaction"}, {ToolTipRole, "toolTip"}, - {AuthorsRole, "authors"}, - {HasLocalUser, "hasLocalUser"}, + {HasLocalMember, "hasLocalMember"}, }; } diff --git a/src/models/reactionmodel.h b/src/models/reactionmodel.h index 5783cc5d1..0825dd025 100644 --- a/src/models/reactionmodel.h +++ b/src/models/reactionmodel.h @@ -5,13 +5,8 @@ #include "neochatroom.h" #include -#include #include - -namespace Quotient -{ -class User; -} +#include /** * @class ReactionModel @@ -30,7 +25,7 @@ public: */ struct Reaction { QString reaction; /**< The reaction emoji. */ - QVariantList authors; /**< The list of authors who sent the given reaction. */ + QList authors; /**< The list of authors who sent the given reaction. */ }; /** @@ -40,8 +35,7 @@ public: TextContentRole = Qt::DisplayRole, /**< The text to show in the reaction. */ ReactionRole, /**< The reaction emoji. */ ToolTipRole, /**< The tool tip to show for the reaction. */ - AuthorsRole, /**< The list of authors who sent the given reaction. */ - HasLocalUser, /**< Whether the local user is in the list of authors. */ + HasLocalMember, /**< Whether the local member is in the list of authors. */ }; explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room); diff --git a/src/models/searchmodel.cpp b/src/models/searchmodel.cpp index 268d90a26..5f6e68518 100644 --- a/src/models/searchmodel.cpp +++ b/src/models/searchmodel.cpp @@ -85,7 +85,7 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const case ShowAuthorRole: return true; case AuthorRole: - return eventHandler.getAuthor(); + return QVariant::fromValue(eventHandler.getAuthor()); case ShowSectionRole: if (row == 0) { return true; diff --git a/src/models/userlistmodel.cpp b/src/models/userlistmodel.cpp index fca7e6abc..9caa9067a 100644 --- a/src/models/userlistmodel.cpp +++ b/src/models/userlistmodel.cpp @@ -26,18 +26,26 @@ void UserListModel::setRoom(NeoChatRoom *room) if (m_currentRoom) { m_currentRoom->disconnect(this); + m_currentRoom->connection()->disconnect(this); } m_currentRoom = room; if (m_currentRoom) { - connect(m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded); - connect(m_currentRoom, &Room::userRemoved, this, &UserListModel::userRemoved); - connect(m_currentRoom, &Room::memberAboutToRename, this, &UserListModel::userRemoved); - connect(m_currentRoom, &Room::memberRenamed, this, &UserListModel::userAdded); - connect(m_currentRoom, &Room::changed, this, &UserListModel::refreshAllUsers); + connect(m_currentRoom, &Room::memberJoined, this, &UserListModel::memberJoined); + connect(m_currentRoom, &Room::memberLeft, this, &UserListModel::memberLeft); + connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) { + refreshMember(member, {DisplayNameRole}); + }); + connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) { + refreshMember(member, {AvatarRole}); + }); + connect(m_currentRoom, &Room::changed, this, &UserListModel::refreshAllMembers); + connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() { + setRoom(nullptr); + }); } - refreshAllUsers(); + refreshAllMembers(); Q_EMIT roomChanged(); } @@ -46,44 +54,36 @@ NeoChatRoom *UserListModel::room() const return m_currentRoom; } -Quotient::User *UserListModel::userAt(QModelIndex index) const -{ - if (index.row() < 0 || index.row() >= m_users.size()) { - return nullptr; - } - return m_users.at(index.row()); -} - QVariant UserListModel::data(const QModelIndex &index, int role) const { if (!index.isValid()) { return QVariant(); } - if (index.row() >= m_users.count()) { + if (index.row() >= m_members.count()) { qDebug() << "UserListModel, something's wrong: index.row() >= " "users.count()"; return {}; } - auto user = m_users.at(index.row()); + auto member = m_members.at(index.row()); if (role == DisplayNameRole) { - return user->displayname(m_currentRoom); + return member.disambiguatedName(); } if (role == UserIdRole) { - return user->id(); + return member.id(); } if (role == AvatarRole) { - return m_currentRoom->avatarForMember(user); + return member.avatarUrl(); } if (role == ObjectRole) { - return QVariant::fromValue(user); + return QVariant::fromValue(member); } if (role == PowerLevelRole) { auto plEvent = m_currentRoom->currentState().get(); if (!plEvent) { return 0; } - return plEvent->powerLevelForUser(user->id()); + return plEvent->powerLevelForUser(member.id()); } if (role == PowerLevelStringRole) { auto pl = m_currentRoom->currentState().get(); @@ -93,7 +93,7 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const return QStringLiteral("Not Available"); } - auto userPl = pl->powerLevelForUser(user->id()); + auto userPl = pl->powerLevelForUser(member.id()); return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.", "%1 (%2)", @@ -109,79 +109,63 @@ int UserListModel::rowCount(const QModelIndex &parent) const if (parent.isValid()) { return 0; } - return m_users.count(); + return m_members.count(); } bool UserListModel::event(QEvent *event) { if (event->type() == QEvent::ApplicationPaletteChange) { - refreshAllUsers(); + refreshAllMembers(); } return QObject::event(event); } -void UserListModel::userAdded(Quotient::User *user) +void UserListModel::memberJoined(const Quotient::RoomMember &member) { - auto pos = findUserPos(user); + auto pos = findUserPos(member); beginInsertRows(QModelIndex(), pos, pos); - m_users.insert(pos, user); + m_members.insert(pos, member); endInsertRows(); - connect(user, &User::defaultAvatarChanged, this, [this, user]() { - refreshUser(user, {AvatarRole}); - }); } -void UserListModel::userRemoved(Quotient::User *user) +void UserListModel::memberLeft(const Quotient::RoomMember &member) { - auto pos = findUserPos(user); - if (pos != m_users.size()) { + auto pos = findUserPos(member); + if (pos != m_members.size()) { beginRemoveRows(QModelIndex(), pos, pos); - m_users.removeAt(pos); + m_members.removeAt(pos); endRemoveRows(); - user->disconnect(this); } else { qWarning() << "Trying to remove a room member not in the user list"; } } -void UserListModel::refreshUser(Quotient::User *user, const QList &roles) +void UserListModel::refreshMember(const Quotient::RoomMember &member, const QList &roles) { - auto pos = findUserPos(user); - if (pos != m_users.size()) { + auto pos = findUserPos(member); + if (pos != m_members.size()) { Q_EMIT dataChanged(index(pos), index(pos), roles); } else { qWarning() << "Trying to access a room member not in the user list"; } } -void UserListModel::refreshAllUsers() +void UserListModel::refreshAllMembers() { beginResetModel(); - for (User *user : std::as_const(m_users)) { - user->disconnect(this); - } - m_users.clear(); + m_members.clear(); if (m_currentRoom != nullptr) { - m_users = m_currentRoom->users(); - std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter()); - - for (User *user : std::as_const(m_users)) { - connect(user, &User::defaultAvatarChanged, this, [this, user]() { - refreshUser(user, {AvatarRole}); - }); - } - connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() { - setRoom(nullptr); - }); + m_members = m_currentRoom->members(); + std::sort(m_members.begin(), m_members.end(), m_currentRoom->memberSorter()); } endResetModel(); Q_EMIT usersRefreshed(); } -int UserListModel::findUserPos(Quotient::User *user) const +int UserListModel::findUserPos(const RoomMember &member) const { - return findUserPos(m_currentRoom->safeMemberName(user->id())); + return findUserPos(member.displayName()); } int UserListModel::findUserPos(const QString &username) const @@ -189,7 +173,7 @@ int UserListModel::findUserPos(const QString &username) const if (!m_currentRoom) { return 0; } - return m_currentRoom->memberSorter().lowerBoundIndex(m_users, username); + return m_currentRoom->memberSorter().lowerBoundIndex(m_members, username); } QHash UserListModel::roleNames() const diff --git a/src/models/userlistmodel.h b/src/models/userlistmodel.h index cc0fd73f4..557f8b656 100644 --- a/src/models/userlistmodel.h +++ b/src/models/userlistmodel.h @@ -56,11 +56,6 @@ public: [[nodiscard]] NeoChatRoom *room() const; void setRoom(NeoChatRoom *room); - /** - * @brief The user at the given index of the model. - */ - [[nodiscard]] Quotient::User *userAt(QModelIndex index) const; - /** * @brief Get the given role value at the given index. * @@ -90,15 +85,15 @@ protected: bool event(QEvent *event) override; private Q_SLOTS: - void userAdded(Quotient::User *user); - void userRemoved(Quotient::User *user); - void refreshUser(Quotient::User *user, const QList &roles = {}); - void refreshAllUsers(); + void memberJoined(const Quotient::RoomMember &member); + void memberLeft(const Quotient::RoomMember &member); + void refreshMember(const Quotient::RoomMember &member, const QList &roles = {}); + void refreshAllMembers(); private: QPointer m_currentRoom; - QList m_users; + QList m_members; - int findUserPos(Quotient::User *user) const; + int findUserPos(const Quotient::RoomMember &member) const; [[nodiscard]] int findUserPos(const QString &username) const; }; diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index 05a194f42..98af6a07b 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -11,9 +11,9 @@ #include #include -#include #include +#include #include #include #include @@ -107,15 +107,15 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS if (this->joinState() != JoinState::Invite) { return; } - auto roomMemberEvent = currentState().get(localUser()->id()); + auto roomMemberEvent = currentState().get(localMember().id()); QImage avatar_image; - if (roomMemberEvent && !user(roomMemberEvent->senderId())->avatarUrl(this).isEmpty()) { - avatar_image = user(roomMemberEvent->senderId())->avatar(128, this); + if (roomMemberEvent && !member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) { + avatar_image = memberAvatar(roomMemberEvent->senderId()).get(this->connection(), 128, [] {}); } else { qWarning() << "using this room's avatar"; avatar_image = avatar(128); } - NotificationsManager::instance().postInviteNotification(this, displayName(), htmlSafeMemberName(roomMemberEvent->senderId()), avatar_image); + NotificationsManager::instance().postInviteNotification(this, displayName(), member(roomMemberEvent->senderId()).htmlSafeDisplayName(), avatar_image); }, Qt::SingleShotConnection); connect(this, &Room::changed, this, [this] { @@ -264,18 +264,16 @@ void NeoChatRoom::forget() QVariantList NeoChatRoom::getUsersTyping() const { - auto users = usersTyping(); - users.removeAll(localUser()); + auto members = membersTyping(); + members.removeAll(localMember()); QVariantList userVariants; - for (const auto &user : users) { - if (connection()->isIgnored(user->id())) { + for (const auto &member : members) { + if (connection()->isIgnored(member.id())) { continue; } userVariants.append(QVariantMap{ - {"id"_ls, user->id()}, - {"avatarMediaId"_ls, user->avatarMediaId(this)}, - {"displayName"_ls, user->displayname(this)}, - {"display"_ls, user->name()}, + {"id"_ls, member.id()}, + {"displayName"_ls, member.displayName()}, }); } return userVariants; @@ -283,7 +281,7 @@ QVariantList NeoChatRoom::getUsersTyping() const void NeoChatRoom::sendTypingNotification(bool isTyping) { - connection()->callApi(BackgroundRequest, localUser()->id(), id(), isTyping, 10000); + connection()->callApi(BackgroundRequest, localMember().id(), id(), isTyping, 10000); } const RoomEvent *NeoChatRoom::lastEvent() const @@ -321,7 +319,7 @@ const RoomEvent *NeoChatRoom::lastEvent() const } } - if (connection()->isIgnored(user(event->senderId()))) { + if (connection()->isIgnored(event->senderId())) { continue; } @@ -381,13 +379,13 @@ bool NeoChatRoom::isEventHighlighted(const RoomEvent *e) const void NeoChatRoom::checkForHighlights(const Quotient::TimelineItem &ti) { - auto localUserId = localUser()->id(); - if (ti->senderId() == localUserId) { + auto localMember = this->localMember(); + if (ti->senderId() == localMember.id()) { return; } if (auto *e = ti.viewAs()) { const auto &text = e->plainBody(); - if (text.contains(localUserId) || text.contains(safeMemberName(localUserId))) { + if (text.contains(localMember.id()) || text.contains(localMember.disambiguatedName())) { highlights.insert(e); } } @@ -433,40 +431,6 @@ QDateTime NeoChatRoom::lastActiveTime() return messageEvents().rbegin()->get()->originTimestamp(); } -// An empty user is useful for returning as a model value to avoid properties being undefined. -static const QVariantMap emptyUser = { - {"isLocalUser"_ls, false}, - {"id"_ls, QString()}, - {"displayName"_ls, QString()}, - {"avatarSource"_ls, QUrl()}, - {"avatarMediaId"_ls, QString()}, - {"color"_ls, QColor()}, - {"object"_ls, QVariant()}, -}; - -QVariantMap NeoChatRoom::getUser(const QString &userID) const -{ - return getUser(user(userID)); -} - -QVariantMap NeoChatRoom::getUser(User *user) const -{ - if (user == nullptr) { - return emptyUser; - } - - return QVariantMap{ - {QStringLiteral("isLocalUser"), user->id() == localUser()->id()}, - {QStringLiteral("id"), user->id()}, - {QStringLiteral("displayName"), user->displayname(this)}, - {QStringLiteral("escapedDisplayName"), htmlSafeMemberName(user->id())}, - {QStringLiteral("avatarSource"), avatarForMember(user)}, - {QStringLiteral("avatarMediaId"), user->avatarMediaId(this)}, - {QStringLiteral("color"), Utils::getUserColor(user->hueF())}, - {QStringLiteral("object"), QVariant::fromValue(user)}, - }; -} - QString NeoChatRoom::avatarMediaId() const { if (const auto avatar = Room::avatarMediaId(); !avatar.isEmpty()) { @@ -474,10 +438,10 @@ QString NeoChatRoom::avatarMediaId() const } // Use the first (excluding self) user's avatar for direct chats - const auto dcUsers = directChatUsers(); - for (const auto u : dcUsers) { - if (u != localUser()) { - return u->avatarMediaId(this); + const auto directChatMembers = this->directChatMembers(); + for (const auto member : directChatMembers) { + if (member != localMember()) { + return member.avatarMediaId(); } } @@ -644,7 +608,7 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction continue; } - if (e->senderId() == localUser()->id()) { + if (e->senderId() == localMember().id()) { redactEventIds.push_back(e->id()); break; } @@ -673,7 +637,7 @@ bool NeoChatRoom::canSendEvent(const QString &eventType) const return false; } auto pl = plEvent->powerLevelForEvent(eventType); - auto currentPl = plEvent->powerLevelForUser(localUser()->id()); + auto currentPl = plEvent->powerLevelForUser(localMember().id()); return currentPl >= pl; } @@ -685,7 +649,7 @@ bool NeoChatRoom::canSendState(const QString &eventType) const return false; } auto pl = plEvent->powerLevelForState(eventType); - auto currentPl = plEvent->powerLevelForUser(localUser()->id()); + auto currentPl = plEvent->powerLevelForUser(localMember().id()); return currentPl >= pl; } @@ -863,7 +827,7 @@ void NeoChatRoom::setUrlPreviewEnabled(const bool &urlPreviewEnabled) * "type": "org.matrix.room.preview_urls", * } */ - connection()->callApi(localUser()->id(), + connection()->callApi(localMember().id(), id(), "org.matrix.room.preview_urls"_ls, QJsonObject{{"disable"_ls, !urlPreviewEnabled}}); @@ -1531,7 +1495,7 @@ void NeoChatRoom::editLastMessage() } // check if the current message's sender's id is same as the user's id - if ((*it)->senderId() == localUser()->id()) { + if ((*it)->senderId() == localMember().id()) { auto content = (*it)->contentJson(); if (e->msgtype() != MessageEventType::Unknown) { @@ -1656,13 +1620,9 @@ int NeoChatRoom::maxRoomVersion() const return maxVersion; } -Quotient::User *NeoChatRoom::directChatRemoteUser() const +Quotient::RoomMember NeoChatRoom::directChatRemoteMember() const { - auto users = connection()->directChatUsers(this); - if (users.isEmpty()) { - return nullptr; - } - return users[0]; + return directChatMembers()[0]; } void NeoChatRoom::sendLocation(float lat, float lon, const QString &description) @@ -1694,20 +1654,6 @@ QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType) return QJsonDocument(accountData(eventType)->fullJson()).toJson(); } -QUrl NeoChatRoom::avatarForMember(Quotient::User *user) const -{ - const auto &url = memberAvatarUrl(user->id()); - if (url.isEmpty() || url.scheme() != "mxc"_ls) { - return {}; - } - auto avatar = connection()->makeMediaUrl(url); - if (avatar.isValid() && avatar.scheme() == QStringLiteral("mxc")) { - return avatar; - } else { - return QUrl(); - } -} - void NeoChatRoom::downloadEventFromServer(const QString &eventId) { if (findInTimeline(eventId) != historyEdge()) { @@ -1779,10 +1725,9 @@ void NeoChatRoom::cleanupExtraEvent(const QString &eventId) m_extraEvents.erase(it); } } - -User *NeoChatRoom::invitingUser() const +QString NeoChatRoom::invitingUserId() const { - return connection()->user(currentState().get(connection()->userId())->senderId()); + return currentState().get(connection()->userId())->senderId(); } void NeoChatRoom::setRoomState(const QString &type, const QString &stateKey, const QByteArray &content) diff --git a/src/neochatroom.h b/src/neochatroom.h index 417741a6e..6a13dfaa0 100644 --- a/src/neochatroom.h +++ b/src/neochatroom.h @@ -10,7 +10,7 @@ #include #include -#include +#include #include "enums/pushrule.h" #include "pollhandler.h" @@ -93,9 +93,9 @@ class NeoChatRoom : public Quotient::Room Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false) /** - * @brief Get a user object for the other person in a direct chat. + * @brief Get a RoomMember object for the other person in a direct chat. */ - Q_PROPERTY(Quotient::User *directChatRemoteUser READ directChatRemoteUser CONSTANT) + Q_PROPERTY(Quotient::RoomMember directChatRemoteMember READ directChatRemoteMember CONSTANT) /** * @brief The Matrix IDs of this room's parents. @@ -235,58 +235,6 @@ public: explicit NeoChatRoom(Quotient::Connection *connection, QString roomId, Quotient::JoinState joinState = {}); - /** - * @brief Get a user in the context of this room. - * - * This is different to getting a Quotient::User object - * as neither of those can provide details like the displayName or avatarMediaId - * without the room context as these can vary from room to room. This function - * provides the room context and outputs the result as QVariantMap. - * - * Can be called with an empty QString to return an empty user, which is a useful return - * from models to avoid undefined properties. - * - * @param userID the ID of the user to output. - * - * @return a QVariantMap for the user with the following properties: - * - isLocalUser - Whether the user is the local user. - * - id - The matrix ID of the user. - * - displayName - Display name in the context of this room. - * - avatarSource - The mxc URL for the user's avatar in the current room. - * - avatarMediaId - Avatar id in the context of this room. - * - color - Color for the user. - * - object - The Quotient::User object for the user. - * - * @sa Quotient::User - */ - Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const; - - /** - * @brief Get a user in the context of this room. - * - * This is different to getting a Quotient::User object - * as neither of those can provide details like the displayName or avatarMediaId - * without the room context as these can vary from room to room. This function - * provides the room context and outputs the result as QVariantMap. - * - * Can be called with a nullptr to return an empty user, which is a useful return - * from models to avoid undefined properties. - * - * @param user the user to output. - * - * @return a QVariantMap for the user with the following properties: - * - isLocalUser - Whether the user is the local user. - * - id - The matrix ID of the user. - * - displayName - Display name in the context of this room. - * - avatarSource - The mxc URL for the user's avatar in the current room. - * - avatarMediaId - Avatar id in the context of this room. - * - color - Color for the user. - * - object - The Quotient::User object for the user. - * - * @sa Quotient::User - */ - Q_INVOKABLE [[nodiscard]] QVariantMap getUser(Quotient::User *user) const; - [[nodiscard]] QVariantList getUsersTyping() const; [[nodiscard]] QDateTime lastActiveTime(); @@ -400,7 +348,7 @@ public: [[nodiscard]] QString avatarMediaId() const; - Quotient::User *directChatRemoteUser() const; + Quotient::RoomMember directChatRemoteMember() const; /** * @brief Whether this room has one or more parent spaces set. @@ -630,8 +578,6 @@ public: */ Q_INVOKABLE QByteArray roomAcountDataJson(const QString &eventType); - Q_INVOKABLE [[nodiscard]] QUrl avatarForMember(Quotient::User *user) const; - /** * @brief Loads the event with the given id from the server and saves it locally. * @@ -660,7 +606,7 @@ public: /** * If we're invited to this room, the user that invited us. Undefined in other cases. */ - Q_INVOKABLE Quotient::User *invitingUser() const; + Q_INVOKABLE QString invitingUserId() const; private: QSet highlights; diff --git a/src/notificationsmanager.cpp b/src/notificationsmanager.cpp index ebb7c9c39..aa12d37ab 100644 --- a/src/notificationsmanager.cpp +++ b/src/notificationsmanager.cpp @@ -108,7 +108,7 @@ void NotificationsManager::processNotificationJob(QPointer co if (!room) { continue; } - auto sender = room->user(notification["event"_ls]["sender"_ls].toString()); + auto sender = room->member(notification["event"_ls]["sender"_ls].toString()); QString body; if (notification["event"_ls]["type"_ls].toString() == "org.matrix.msc3381.poll.start"_ls) { @@ -124,13 +124,13 @@ void NotificationsManager::processNotificationJob(QPointer co } QImage avatar_image; - if (!sender->avatarUrl(room).isEmpty()) { - avatar_image = sender->avatar(128, room); + if (!sender.avatarUrl().isEmpty()) { + avatar_image = room->memberAvatar(sender.id()).get(connection, 128, {}); } else { avatar_image = room->avatar(128); } postNotification(dynamic_cast(room), - sender->displayname(room), + sender.displayName(), body, avatar_image, notification["event"_ls].toObject()["event_id"_ls].toString(), @@ -213,7 +213,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room, if (!room) { return; } - auto connection = dynamic_cast(Controller::instance().accounts().get(room->localUser()->id())); + auto connection = dynamic_cast(Controller::instance().accounts().get(room->localMember().id())); Controller::instance().setActiveConnection(connection); RoomManager::instance().setConnection(connection); RoomManager::instance().resolveResource(room->id()); @@ -230,7 +230,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room, notification->setReplyAction(std::move(replyAction)); } - notification->setHint(QStringLiteral("x-kde-origin-name"), room->localUser()->id()); + notification->setHint(QStringLiteral("x-kde-origin-name"), room->localMember().id()); notification->sendEvent(); } @@ -276,7 +276,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QS return; } RoomManager::instance().leaveRoom(room); - room->connection()->addToIgnoredUsers(room->invitingUser()); + room->connection()->addToIgnoredUsers(room->invitingUserId()); notification->close(); }); connect(notification, &KNotification::closed, this, [this, room]() { @@ -286,7 +286,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QS m_invitations.remove(room->id()); }); - notification->setHint(QStringLiteral("x-kde-origin-name"), room->localUser()->id()); + notification->setHint(QStringLiteral("x-kde-origin-name"), room->localMember().id()); notification->sendEvent(); } diff --git a/src/pollhandler.cpp b/src/pollhandler.cpp index 463cbc5fb..30911e220 100644 --- a/src/pollhandler.cpp +++ b/src/pollhandler.cpp @@ -154,7 +154,7 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId return; } QStringList ownAnswers; - for (const auto &answer : m_answers[room->localUser()->id()].toArray()) { + for (const auto &answer : m_answers[room->localMember().id()].toArray()) { ownAnswers += answer.toString(); } if (ownAnswers.contains(answerId)) { @@ -169,7 +169,7 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId } auto response = new PollResponseEvent(eventId, ownAnswers); - handleAnswer(response->contentJson(), room->localUser()->id(), QDateTime::currentDateTime()); + handleAnswer(response->contentJson(), room->localMember().id(), QDateTime::currentDateTime()); room->postEvent(response); } diff --git a/src/qml/ContextMenu.qml b/src/qml/ContextMenu.qml index a6cd0d558..4b1c93ab3 100644 --- a/src/qml/ContextMenu.qml +++ b/src/qml/ContextMenu.qml @@ -49,7 +49,7 @@ Loader { text: room.isDirectChat() ? i18nc("@action:inmenu", "Copy user's Matrix ID to Clipboard") : i18nc("@action:inmenu", "Copy Address to Clipboard") icon.name: "edit-copy" onTriggered: if (room.isDirectChat()) { - Clipboard.saveText(room.directChatRemoteUser.id); + Clipboard.saveText(room.directChatRemoteMember.id); } else if (room.canonicalAlias.length === 0) { Clipboard.saveText(room.id); } else { diff --git a/src/qml/DelegateContextMenu.qml b/src/qml/DelegateContextMenu.qml index 224c15aab..e12781807 100644 --- a/src/qml/DelegateContextMenu.qml +++ b/src/qml/DelegateContextMenu.qml @@ -40,18 +40,9 @@ Loader { /** * @brief The message author. * - * This should consist of the following: - * - id - The matrix ID of the author. - * - isLocalUser - Whether the author is the local user. - * - avatarSource - The mxc URL for the author's avatar in the current room. - * - avatarMediaId - The media ID of the author's avatar. - * - avatarUrl - The mxc URL for the author's avatar. - * - displayName - The display name of the author. - * - display - The name of the author. - * - color - The color for the author. - * - object - The Quotient::User object for the author. + * A Quotient::RoomMember object. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ required property var author @@ -90,7 +81,7 @@ Loader { } component RemoveMessageAction: Kirigami.Action { - visible: author.isLocalUser || currentRoom.canSendState("redact") + visible: author.isLocalMember || currentRoom.canSendState("redact") text: i18n("Remove") icon.name: "edit-delete-remove" icon.color: "red" @@ -116,7 +107,7 @@ Loader { component ReportMessageAction: Kirigami.Action { text: i18nc("@action:button 'Report' as in 'Report this event to the administrators'", "Report") icon.name: "dialog-warning-symbolic" - visible: !author.isLocalUser + visible: !author.isLocalMember onTriggered: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReportSheet'), { room: currentRoom, eventId: eventId diff --git a/src/qml/DirectChatDrawerHeader.qml b/src/qml/DirectChatDrawerHeader.qml index 04a046929..a0873fd16 100644 --- a/src/qml/DirectChatDrawerHeader.qml +++ b/src/qml/DirectChatDrawerHeader.qml @@ -33,7 +33,7 @@ ColumnLayout { Layout.alignment: Qt.AlignHCenter onClicked: { - RoomManager.resolveResource(root.room.directChatRemoteUser.id, "mention"); + RoomManager.resolveResource(root.room.directChatRemoteMember.uri) } contentItem: KirigamiComponents.Avatar { diff --git a/src/qml/FileDelegateContextMenu.qml b/src/qml/FileDelegateContextMenu.qml index b863a66cb..d24ca547e 100644 --- a/src/qml/FileDelegateContextMenu.qml +++ b/src/qml/FileDelegateContextMenu.qml @@ -63,7 +63,7 @@ DelegateContextMenu { } }, Kirigami.Action { - visible: author.id === currentRoom.localUser.id || currentRoom.canSendState("redact") + visible: author.id === currentRoom.localMember.id || currentRoom.canSendState("redact") text: i18n("Remove") icon.name: "edit-delete-remove" icon.color: "red" diff --git a/src/qml/InvitationView.qml b/src/qml/InvitationView.qml index 410882fc3..ba658c4e5 100644 --- a/src/qml/InvitationView.qml +++ b/src/qml/InvitationView.qml @@ -22,7 +22,7 @@ Kirigami.PlaceholderMessage { onClicked: { RoomManager.leaveRoom(root.currentRoom); - root.currentRoom.connection.addToIgnoredUsers(root.currentRoom.invitingUser()); + root.currentRoom.connection.addToIgnoredUsers(root.currentRoom.invitingUserId()); } } QQC2.Button { diff --git a/src/qml/LocationMapItem.qml b/src/qml/LocationMapItem.qml index 9b536ef62..19b2fdc02 100644 --- a/src/qml/LocationMapItem.qml +++ b/src/qml/LocationMapItem.qml @@ -55,7 +55,7 @@ MapQuickItem { width: height height: parent.height / 3 + 1 name: root.author.displayName - source: root.author.avatarSource + source: root.author.avatarUrl color: root.author.color } diff --git a/src/qml/Main.qml b/src/qml/Main.qml index 5311fd986..318446053 100644 --- a/src/qml/Main.qml +++ b/src/qml/Main.qml @@ -354,7 +354,7 @@ Kirigami.ApplicationWindow { function showUserDetail(user) { Qt.createComponent("org.kde.neochat", "UserDetailDialog").createObject(root.QQC2.ApplicationWindow.window, { room: RoomManager.currentRoom ? RoomManager.currentRoom : null, - user: RoomManager.currentRoom ? RoomManager.currentRoom.getUser(user.id) : QmlUtils.getUser(user), + user: user, connection: root.connection }).open(); } diff --git a/src/qml/MessageDelegateContextMenu.qml b/src/qml/MessageDelegateContextMenu.qml index d864b4d15..1d622b429 100644 --- a/src/qml/MessageDelegateContextMenu.qml +++ b/src/qml/MessageDelegateContextMenu.qml @@ -40,7 +40,7 @@ DelegateContextMenu { currentRoom.editCache.editId = eventId; currentRoom.mainCache.replyId = ""; } - visible: author.isLocalUser && (root.messageComponentType === MessageComponentType.Emote || root.messageComponentType === MessageComponentType.Message) + visible: author.isLocalMember && (root.messageComponentType === MessageComponentType.Emote || root.messageComponentType === MessageComponentType.Message) }, DelegateContextMenu.ReplyMessageAction {}, Kirigami.Action { diff --git a/src/qml/UserDetailDialog.qml b/src/qml/UserDetailDialog.qml index 0f3e89072..0cb057f20 100644 --- a/src/qml/UserDetailDialog.qml +++ b/src/qml/UserDetailDialog.qml @@ -19,8 +19,21 @@ Kirigami.Dialog { // This dialog is sometimes used outside the context of a room, e.g., when scanning a user's QR code. // Make sure that code is prepared to deal with this property being null property NeoChatRoom room + + /** + * @brief The user's profile object. + * + * Required to interact with the profile and perform action like ignoring. + */ property var user + /** + * @brief The RoomMember object for the user in the current room. + * + * Required to visualise the user. + */ + property var member: root.room.member(user.id) + property NeoChatConnection connection leftPadding: 0 @@ -48,9 +61,9 @@ Kirigami.Dialog { Layout.preferredWidth: Kirigami.Units.iconSizes.huge Layout.preferredHeight: Kirigami.Units.iconSizes.huge - name: root.user.displayName - source: root.user.avatarSource - color: root.user.color + name: root.member.displayName + source: root.member.avatarUrl + color: root.member.color } ColumnLayout { @@ -69,7 +82,7 @@ Kirigami.Dialog { Kirigami.SelectableLabel { textFormat: TextEdit.PlainText - text: root.user.id + text: root.member.id } } QQC2.AbstractButton { @@ -78,16 +91,16 @@ Kirigami.Dialog { contentItem: Barcode { id: barcode barcodeType: Barcode.QRCode - content: "https://matrix.to/#/" + root.user.id + content: "https://matrix.to/#/" + root.member.id } onClicked: { let map = qrMaximizeComponent.createObject(parent, { text: barcode.content, - title: root.user.displayName, - subtitle: root.user.id, - avatarColor: root.user.color, - avatarSource: root.user.avatarSource + title: root.member.displayName, + subtitle: root.member.id, + avatarColor: root.member.color, + avatarSource: root.member.avatarUrl, }); root.close(); map.open(); @@ -104,46 +117,46 @@ Kirigami.Dialog { } FormCard.FormButtonDelegate { - visible: !root.user.isLocalUser && !!root.user.object + visible: !root.member.isLocalMember action: Kirigami.Action { - text: !!root.user.object && root.connection.isIgnored(root.user.object) ? i18n("Unignore this user") : i18n("Ignore this user") + text: !!root.user && root.connection.isIgnored(root.user) ? i18n("Unignore this user") : i18n("Ignore this user") icon.name: "im-invisible-user" onTriggered: { root.close(); - root.connection.isIgnored(root.user.object) ? root.connection.removeFromIgnoredUsers(root.user.object) : root.connection.addToIgnoredUsers(root.user.object); + root.connection.isIgnored(root.user) ? root.connection.removeFromIgnoredUsers(root.user) : root.connection.addToIgnoredUsers(root.user); } } } FormCard.FormButtonDelegate { - visible: root.room && !root.user.isLocalUser && room.canSendState("kick") && room.containsUser(root.user.id) && room.getUserPowerLevel(root.user.id) < room.getUserPowerLevel(root.connection.localUser.id) + visible: root.room && !root.member.isLocalMember && room.canSendState("kick") && room.containsUser(root.member.id) && room.getUserPowerLevel(root.member.id) < room.getUserPowerLevel(root.room.localMember.id) action: Kirigami.Action { text: i18n("Kick this user") icon.name: "im-kick-user" onTriggered: { - root.room.kickMember(root.user.id); + root.room.kickMember(root.member.id); root.close(); } } } FormCard.FormButtonDelegate { - visible: root.room && !root.user.isLocalUser && room.canSendState("invite") && !room.containsUser(root.user.id) + visible: root.room && !root.member.isLocalMember && room.canSendState("invite") && !room.containsUser(root.member.id) action: Kirigami.Action { - enabled: root.room && !root.room.isUserBanned(root.user.id) + enabled: root.room && !root.room.isUserBanned(root.member.id) text: i18n("Invite this user") icon.name: "list-add-user" onTriggered: { - root.room.inviteToRoom(root.user.id); + root.room.inviteToRoom(root.member.id); root.close(); } } } FormCard.FormButtonDelegate { - visible: root.room && !root.user.isLocalUser && room.canSendState("ban") && !room.isUserBanned(root.user.id) && room.getUserPowerLevel(root.user.id) < room.getUserPowerLevel(root.room.connection.localUser.id) + visible: root.room && !root.member.isLocalMember && room.canSendState("ban") && !room.isUserBanned(root.member.id) && room.getUserPowerLevel(root.member.id) < room.getUserPowerLevel(root.room.localMember.id) action: Kirigami.Action { text: i18n("Ban this user") @@ -152,7 +165,7 @@ Kirigami.Dialog { onTriggered: { (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'BanSheet'), { room: root.room, - userId: root.user.id + userId: root.member.id }, { title: i18nc("@title", "Ban User"), width: Kirigami.Units.gridUnit * 25 @@ -163,14 +176,14 @@ Kirigami.Dialog { } FormCard.FormButtonDelegate { - visible: root.room && !root.user.isLocalUser && room.canSendState("ban") && room.isUserBanned(root.user.id) + visible: root.room && !root.member.isLocalMember && room.canSendState("ban") && room.isUserBanned(root.member.id) action: Kirigami.Action { text: i18n("Unban this user") icon.name: "im-irc" icon.color: Kirigami.Theme.negativeTextColor onTriggered: { - root.room.unban(root.user.id); + root.room.unban(root.member.id); root.close(); } } @@ -184,8 +197,8 @@ Kirigami.Dialog { onTriggered: { let dialog = powerLevelDialog.createObject(this, { room: root.room, - userId: root.user.id, - powerLevel: root.room.getUserPowerLevel(root.user.id) + userId: root.member.id, + powerLevel: root.room.getUserPowerLevel(root.member.id) }); dialog.open(); root.close(); @@ -201,7 +214,7 @@ Kirigami.Dialog { } FormCard.FormButtonDelegate { - visible: root.room && (root.user.isLocalUser || room.canSendState("redact")) + visible: root.room && (root.member.isLocalUser || room.canSendState("redact")) action: Kirigami.Action { text: i18n("Remove recent messages by this user") @@ -210,7 +223,7 @@ Kirigami.Dialog { onTriggered: { applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RemoveSheet'), { room: root.room, - userId: root.user.id + userId: root.member.id }, { title: i18nc("@title", "Remove Messages"), width: Kirigami.Units.gridUnit * 25 @@ -221,12 +234,12 @@ Kirigami.Dialog { } FormCard.FormButtonDelegate { - visible: !root.user.isLocalUser + visible: !root.member.isLocalMember action: Kirigami.Action { text: root.connection.directChatExists(root.user.object) ? i18nc("%1 is the name of the user.", "Chat with %1", root.user.escapedDisplayName) : i18n("Invite to private chat") icon.name: "document-send" onTriggered: { - root.connection.openOrCreateDirectChat(root.user.object); + root.room.connection.openOrCreateDirectChat(root.user); root.close(); } } @@ -237,7 +250,7 @@ Kirigami.Dialog { text: i18n("Copy link") icon.name: "username-copy" onTriggered: { - Clipboard.saveText("https://matrix.to/#/" + root.user.id); + Clipboard.saveText("https://matrix.to/#/" + root.member.id); } } } diff --git a/src/roommanager.cpp b/src/roommanager.cpp index 5d8a41b1a..adea379f5 100644 --- a/src/roommanager.cpp +++ b/src/roommanager.cpp @@ -256,7 +256,6 @@ void RoomManager::openRoomForActiveConnection() UriResolveResult RoomManager::visitUser(User *user, const QString &action) { if (action == "mention"_ls || action.isEmpty()) { - // send it has QVariantMap because the properties in the user->load(); Q_EMIT showUserDetail(user); } else if (action == "_interactive"_ls) { diff --git a/src/roommanager.h b/src/roommanager.h index 7c4e9264f..ee69ea33d 100644 --- a/src/roommanager.h +++ b/src/roommanager.h @@ -8,6 +8,7 @@ #include #include #include +#include #include #include "chatdocumenthandler.h" @@ -290,7 +291,7 @@ Q_SIGNALS: * @brief Request to show a menu for the given event. */ void showMessageMenu(const QString &eventId, - const QVariantMap &author, + const Quotient::RoomMember &author, MessageComponentType::Type messageComponentType, const QString &plainText, const QString &htmlText, @@ -300,7 +301,7 @@ Q_SIGNALS: * @brief Request to show a menu for the given media event. */ void showFileMenu(const QString &eventId, - const QVariantMap &author, + const Quotient::RoomMember &author, MessageComponentType::Type messageComponentType, const QString &plainText, const QString &mimeType, diff --git a/src/settings/Permissions.qml b/src/settings/Permissions.qml index 3c25d579e..c21e09219 100644 --- a/src/settings/Permissions.qml +++ b/src/settings/Permissions.qml @@ -61,8 +61,8 @@ FormCard.FormCardPage { spacing: Kirigami.Units.largeSpacing QQC2.Label { id: powerLevelLabel - visible: !room.canSendState("m.room.power_levels") || (room.getUserPowerLevel(room.localUser.id) <= privilegedUserDelegate.powerLevel && privilegedUserDelegate.userId != room.localUser.id) text: privilegedUserDelegate.powerLevelString + visible: !room.canSendState("m.room.power_levels") || (room.getUserPowerLevel(room.localMember.id) <= privilegedUserDelegate.powerLevel && privilegedUserDelegate.userId != room.localMember.id) color: Kirigami.Theme.disabledTextColor } QQC2.ComboBox { diff --git a/src/texthandler.cpp b/src/texthandler.cpp index f2c706e46..f15dcc203 100644 --- a/src/texthandler.cpp +++ b/src/texthandler.cpp @@ -691,9 +691,9 @@ QString TextHandler::emoteString(const NeoChatRoom *room, const Quotient::RoomEv } auto e = eventCast(event); - auto author = room->user(e->senderId()); - return QStringLiteral("* senderId() + QStringLiteral("\" style=\"color:") + Utils::getUserColor(author->hueF()).name() - + QStringLiteral("\">") + author->displayname(room) + QStringLiteral(" "); + auto author = room->member(e->senderId()); + return QStringLiteral("* senderId() + QStringLiteral("\" style=\"color:") + author.color().name() + + QStringLiteral("\">") + author.htmlSafeDisplayName() + QStringLiteral(" "); } QString TextHandler::convertCodeLanguageString(const QString &languageString) diff --git a/src/timeline/AvatarFlow.qml b/src/timeline/AvatarFlow.qml index 0bfa5a390..2285f6cb4 100644 --- a/src/timeline/AvatarFlow.qml +++ b/src/timeline/AvatarFlow.qml @@ -25,7 +25,7 @@ Flow { implicitHeight: root.avatarSize name: modelData.displayName - source: modelData.avatarSource + source: modelData.avatarUrl color: modelData.color } } diff --git a/src/timeline/Bubble.qml b/src/timeline/Bubble.qml index 0f2a525a4..bbb96cf8b 100644 --- a/src/timeline/Bubble.qml +++ b/src/timeline/Bubble.qml @@ -35,18 +35,9 @@ QQC2.Control { /** * @brief The message author. * - * This should consist of the following: - * - id - The matrix ID of the author. - * - isLocalUser - Whether the author is the local user. - * - avatarSource - The mxc URL for the author's avatar in the current room. - * - avatarMediaId - The media ID of the author's avatar. - * - avatarUrl - The mxc URL for the author's avatar. - * - displayName - The display name of the author. - * - display - The name of the author. - * - color - The color for the author. - * - object - The Quotient::User object for the author. + * A Quotient::RoomMember object. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ property var author @@ -125,14 +116,14 @@ QQC2.Control { id: nameButton Layout.fillWidth: true contentItem: QQC2.Label { - text: root.author.displayName + text: root.author.disambiguatedName color: root.author.color textFormat: Text.PlainText font.weight: Font.Bold elide: Text.ElideRight } Accessible.name: contentItem.text - onClicked: RoomManager.resolveResource(root.author.id, "mention") + onClicked: RoomManager.resolveResource(root.author.uri) } QQC2.Label { id: timeLabel @@ -176,7 +167,7 @@ QQC2.Control { visible: root.showBackground Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.inherit: false - color: if (root.author.isLocalUser) { + color: if (root.author.isLocalMember) { return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15); } else if (root.showHighlight) { return Kirigami.Theme.positiveBackgroundColor; diff --git a/src/timeline/CodeComponent.qml b/src/timeline/CodeComponent.qml index 6f07041e3..83f2be110 100644 --- a/src/timeline/CodeComponent.qml +++ b/src/timeline/CodeComponent.qml @@ -16,18 +16,9 @@ QQC2.Control { /** * @brief The message author. * - * This should consist of the following: - * - id - The matrix ID of the author. - * - isLocalUser - Whether the author is the local user. - * - avatarSource - The mxc URL for the author's avatar in the current room. - * - avatarMediaId - The media ID of the author's avatar. - * - avatarUrl - The mxc URL for the author's avatar. - * - displayName - The display name of the author. - * - display - The name of the author. - * - color - The color for the author. - * - object - The Quotient::User object for the author. + * A Quotient::RoomMember object. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ required property var author diff --git a/src/timeline/LocationComponent.qml b/src/timeline/LocationComponent.qml index 49c847f9b..d33a2c3c0 100644 --- a/src/timeline/LocationComponent.qml +++ b/src/timeline/LocationComponent.qml @@ -19,18 +19,9 @@ ColumnLayout { /** * @brief The message author. * - * This should consist of the following: - * - id - The matrix ID of the author. - * - isLocalUser - Whether the author is the local user. - * - avatarSource - The mxc URL for the author's avatar in the current room. - * - avatarMediaId - The media ID of the author's avatar. - * - avatarUrl - The mxc URL for the author's avatar. - * - displayName - The display name of the author. - * - display - The name of the author. - * - color - The color for the author. - * - object - The Quotient::User object for the author. + * A Quotient::RoomMember object. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ required property var author diff --git a/src/timeline/MessageDelegate.qml b/src/timeline/MessageDelegate.qml index 51640385e..24b1b94f8 100644 --- a/src/timeline/MessageDelegate.qml +++ b/src/timeline/MessageDelegate.qml @@ -56,18 +56,9 @@ TimelineDelegate { /** * @brief The message author. * - * This should consist of the following: - * - id - The matrix ID of the author. - * - isLocalUser - Whether the author is the local user. - * - avatarSource - The mxc URL for the author's avatar in the current room. - * - avatarMediaId - The media ID of the author's avatar. - * - avatarUrl - The mxc URL for the author's avatar. - * - displayName - The display name of the author. - * - display - The name of the author. - * - color - The color for the author. - * - object - The Quotient::User object for the author. + * A Quotient::RoomMember object. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ required property var author @@ -281,11 +272,11 @@ TimelineDelegate { visible: (root.showAuthor || root.alwaysShowAuthor) && Config.showAvatarInTimeline && (Config.compactLayout || !_private.showUserMessageOnRight) name: root.author.displayName - source: root.author.avatarSource + source: root.author.avatarUrl color: root.author.color - QQC2.ToolTip.text: root.author.escapedDisplayName + QQC2.ToolTip.text: root.author.htmlSafeDisambiguatedName - onClicked: RoomManager.resolveResource(root.author.id, "mention") + onClicked: RoomManager.resolveResource(root.author.uri) } Bubble { id: bubble @@ -411,7 +402,7 @@ TimelineDelegate { /** * @brief Whether local user messages should be aligned right. */ - property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalUser && !Config.compactLayout && !root.alwaysFillWidth + property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && root.author.isLocalMember && !Config.compactLayout && !root.alwaysFillWidth function showMessageMenu() { RoomManager.viewEventMenu(root.eventId, root.room, root.selectedText); diff --git a/src/timeline/PollComponent.qml b/src/timeline/PollComponent.qml index f78d72fca..6a7bd3a7f 100644 --- a/src/timeline/PollComponent.qml +++ b/src/timeline/PollComponent.qml @@ -52,7 +52,7 @@ ColumnLayout { delegate: RowLayout { Layout.fillWidth: true CheckBox { - checked: root.pollHandler.answers[root.room.localUser.id] ? root.pollHandler.answers[root.room.localUser.id].includes(modelData["id"]) : false + checked: root.pollHandler.answers[root.room.localember.id] ? root.pollHandler.answers[root.room.localMember.id].includes(modelData["id"]) : false onClicked: root.pollHandler.sendPollAnswer(root.eventId, modelData["id"]) enabled: !root.pollHandler.hasEnded } diff --git a/src/timeline/ReactionDelegate.qml b/src/timeline/ReactionDelegate.qml index 3b1ba1755..0ec944b8d 100644 --- a/src/timeline/ReactionDelegate.qml +++ b/src/timeline/ReactionDelegate.qml @@ -35,7 +35,7 @@ Flow { required property string textContent required property string reaction required property string toolTip - required property bool hasLocalUser + required property bool hasLocalMember width: Math.max(contentItem.implicitWidth + leftPadding + rightPadding, height) height: Math.round(Kirigami.Units.gridUnit * 1.5) @@ -54,13 +54,13 @@ Flow { padding: Kirigami.Units.smallSpacing background: Kirigami.ShadowedRectangle { - color: reactionDelegate.hasLocalUser ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor + color: reactionDelegate.hasLocalMember ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor Kirigami.Theme.inherit: false Kirigami.Theme.colorSet: Config.compactLayout ? Kirigami.Theme.Window : Kirigami.Theme.View radius: height / 2 shadow { size: Kirigami.Units.smallSpacing - color: !reactionDelegate.hasLocalUser ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10) + color: !reactionDelegate.hasLocalMember ? Qt.rgba(0.0, 0.0, 0.0, 0.10) : Qt.rgba(Kirigami.Theme.textColor.r, Kirigami.Theme.textColor.g, Kirigami.Theme.textColor.b, 0.10) } } diff --git a/src/timeline/ReplyComponent.qml b/src/timeline/ReplyComponent.qml index c5e2b3905..5932be367 100644 --- a/src/timeline/ReplyComponent.qml +++ b/src/timeline/ReplyComponent.qml @@ -32,18 +32,9 @@ RowLayout { /** * @brief The reply author. * - * This should consist of the following: - * - id - The matrix ID of the reply author. - * - isLocalUser - Whether the reply author is the local user. - * - avatarSource - The mxc URL for the reply author's avatar in the current room. - * - avatarMediaId - The media ID of the reply author's avatar. - * - avatarUrl - The mxc URL for the reply author's avatar. - * - displayName - The display name of the reply author. - * - display - The name of the reply author. - * - color - The color for the reply author. - * - object - The Quotient::User object for the reply author. + * A Quotient::RoomMember object. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ required property var replyAuthor @@ -87,7 +78,7 @@ RowLayout { implicitWidth: Kirigami.Units.iconSizes.small implicitHeight: Kirigami.Units.iconSizes.small - source: root.replyAuthor.avatarSource + source: root.replyAuthor.avatarUrl name: root.replyAuthor.displayName color: root.replyAuthor.color } diff --git a/src/timeline/StateComponent.qml b/src/timeline/StateComponent.qml index ccc433b7c..06ab3cf12 100644 --- a/src/timeline/StateComponent.qml +++ b/src/timeline/StateComponent.qml @@ -24,18 +24,9 @@ RowLayout { /** * @brief The message author. * - * This should consist of the following: - * - id - The matrix ID of the author. - * - isLocalUser - Whether the author is the local user. - * - avatarSource - The mxc URL for the author's avatar in the current room. - * - avatarMediaId - The media ID of the author's avatar. - * - avatarUrl - The mxc URL for the author's avatar. - * - displayName - The display name of the author. - * - display - The name of the author. - * - color - The color for the author. - * - object - The Quotient::User object for the author. + * A Quotient::RoomMember object. * - * @sa Quotient::User + * @sa Quotient::RoomMember */ property var author: modelData.author diff --git a/src/timeline/StateDelegate.qml b/src/timeline/StateDelegate.qml index 7b2a90d1d..e2131e2b4 100644 --- a/src/timeline/StateDelegate.qml +++ b/src/timeline/StateDelegate.qml @@ -113,7 +113,7 @@ TimelineDelegate { implicitHeight: Kirigami.Units.iconSizes.small name: parent.modelData.displayName - source: parent.modelData.avatarSource + source: parent.modelData.avatarUrl color: parent.modelData.color } } diff --git a/src/utils.cpp b/src/utils.cpp index b07922230..9ad03aedb 100644 --- a/src/utils.cpp +++ b/src/utils.cpp @@ -7,45 +7,6 @@ #include -using namespace Quotient; - -static const QVariantMap emptyUser = { - {"isLocalUser"_ls, false}, - {"id"_ls, QString()}, - {"displayName"_ls, QString()}, - {"avatarSource"_ls, QUrl()}, - {"avatarMediaId"_ls, QString()}, - {"color"_ls, QColor()}, - {"object"_ls, QVariant()}, -}; - -QVariantMap QmlUtils::getUser(User *user) const -{ - if (user == nullptr) { - return emptyUser; - } - - const auto &url = user->avatarUrl(); - if (url.isEmpty() || url.scheme() != "mxc"_ls) { - return {}; - } - auto avatarSource = user->connection()->makeMediaUrl(url); - if (!avatarSource.isValid() || avatarSource.scheme() != QStringLiteral("mxc")) { - avatarSource = {}; - } - - return QVariantMap{ - {QStringLiteral("isLocalUser"), user->id() == user->connection()->user()->id()}, - {QStringLiteral("id"), user->id()}, - {QStringLiteral("displayName"), user->displayname()}, - {QStringLiteral("escapedDisplayName"), user->displayname().toHtmlEscaped()}, - {QStringLiteral("avatarSource"), avatarSource}, - {QStringLiteral("avatarMediaId"), user->avatarMediaId()}, - {QStringLiteral("color"), Utils::getUserColor(user->hueF())}, - {QStringLiteral("object"), QVariant::fromValue(user)}, - }; -} - bool QmlUtils::isValidJson(const QByteArray &json) { return !QJsonDocument::fromJson(json).isNull(); diff --git a/src/utils.h b/src/utils.h index 1fc0e3e78..87eb52704 100644 --- a/src/utils.h +++ b/src/utils.h @@ -3,14 +3,9 @@ #pragma once -#include -#include -#include #include #include -#include - class QmlUtils : public QObject { Q_OBJECT @@ -30,31 +25,12 @@ public: return _instance; } - Q_INVOKABLE QVariantMap getUser(Quotient::User *user) const; Q_INVOKABLE bool isValidJson(const QByteArray &json); private: QmlUtils() = default; }; - -namespace Utils -{ - -/** - * @brief Get a color for a user from a hueF value. - * - * The lightness of the color will be modified depending on the current palette in - * order to maintain contrast. - */ -inline QColor getUserColor(qreal hueF) -{ - const auto lightness = static_cast(QGuiApplication::instance())->palette().color(QPalette::Active, QPalette::Window).lightnessF(); - // https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal - return QColor::fromHslF(hueF, 1, -0.7 * lightness + 0.9, 1); -} -} - namespace TextRegex { static const QRegularExpression endTagType{QStringLiteral("(>| )")};