Improve User Avatar Model Roles

- Update the message event and user models so that the full source url is output.
- Separate the reply author into its own role
- Create an empty user object that can be passed so that the QML code no longer needs to check if certain parameters exist.
- Make avatarForMember return and empty QUrl if a valid avatar cannot be found and make use in the user and event models

As well as cleaning up the QML this should also stop the QML Image: Media id '' doesn't follow server/mediaId pattern spam in the log.
This commit is contained in:
James Graham
2023-04-30 13:51:06 +00:00
parent 1bb03e115e
commit 2281fe6f8a
13 changed files with 83 additions and 45 deletions

View File

@@ -47,6 +47,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
roles[FileMimetypeIcon] = "fileMimetypeIcon"; roles[FileMimetypeIcon] = "fileMimetypeIcon";
roles[EventResolvedTypeRole] = "eventResolvedType"; roles[EventResolvedTypeRole] = "eventResolvedType";
roles[IsReplyRole] = "isReply"; roles[IsReplyRole] = "isReply";
roles[ReplyAuthor] = "replyAuthor";
roles[ReplyRole] = "reply"; roles[ReplyRole] = "reply";
roles[ReplyIdRole] = "replyId"; roles[ReplyIdRole] = "replyId";
roles[ShowAuthorRole] = "showAuthor"; roles[ShowAuthorRole] = "showAuthor";
@@ -421,12 +422,24 @@ void MessageEventModel::fetchMore(const QModelIndex &parent)
} }
} }
inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomEvent &evt) static const QVariantMap emptyUser = {
{"isLocalUser", false},
{"id", QString()},
{"avatarSource", QUrl()},
{"avatarMediaId", QString()},
{"avatarUrl", QString()},
{"displayName", QString()},
{"display", QString()},
{"color", QColor()},
{"object", QVariant()},
};
inline QVariantMap userInContext(NeoChatUser *user, NeoChatRoom *room)
{ {
Q_UNUSED(evt)
return QVariantMap{ return QVariantMap{
{"isLocalUser", user->id() == room->localUser()->id()}, {"isLocalUser", user->id() == room->localUser()->id()},
{"id", user->id()}, {"id", user->id()},
{"avatarSource", room->avatarForMember(user)},
{"avatarMediaId", user->avatarMediaId(room)}, {"avatarMediaId", user->avatarMediaId(room)},
{"avatarUrl", user->avatarUrl(room)}, {"avatarUrl", user->avatarUrl(room)},
{"displayName", user->displayname(room)}, {"displayName", user->displayname(room)},
@@ -551,7 +564,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
if (role == AuthorRole) { if (role == AuthorRole) {
auto author = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId())); auto author = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
return userAtEvent(author, m_currentRoom, evt); return userInContext(author, m_currentRoom);
} }
if (role == ContentTypeRole) { if (role == ContentTypeRole) {
@@ -690,21 +703,19 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString(); return evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
} }
if (role == ReplyRole) { if (role == ReplyAuthor) {
const QString &replyEventId = evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString(); auto replyPtr = getReplyForEvent(evt);
if (replyEventId.isEmpty()) {
return {}; if (replyPtr) {
}; auto replyUser = static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId()));
const auto replyIt = m_currentRoom->findInTimeline(replyEventId); return userInContext(replyUser, m_currentRoom);
const RoomEvent *replyPtr = replyIt != m_currentRoom->historyEdge() ? &**replyIt : nullptr; } else {
if (!replyPtr) { return emptyUser;
for (const auto &e : m_extraEvents) {
if (e->id() == replyEventId) {
replyPtr = e.get();
break;
}
}
} }
}
if (role == ReplyRole) {
auto replyPtr = getReplyForEvent(evt);
if (!replyPtr) { if (!replyPtr) {
return {}; return {};
} }
@@ -753,11 +764,12 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
content = QVariant::fromValue(e->image().originalJson); content = QVariant::fromValue(e->image().originalJson);
} }
return QVariantMap{{"eventId", replyEventId}, return QVariantMap{
{"display", m_currentRoom->eventToString(*replyPtr, Qt::RichText)}, {"eventId", replyPtr->id()},
{"content", content}, {"display", m_currentRoom->eventToString(*replyPtr, Qt::RichText)},
{"type", type}, {"content", content},
{"author", userAtEvent(static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId())), m_currentRoom, evt)}}; {"type", type},
};
} }
if (role == ShowAuthorRole) { if (role == ShowAuthorRole) {
@@ -835,7 +847,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
#else #else
auto user = static_cast<NeoChatUser *>(userId); auto user = static_cast<NeoChatUser *>(userId);
#endif #endif
users += userAtEvent(user, m_currentRoom, evt); users += userInContext(user, m_currentRoom);
} }
return users; return users;
@@ -901,7 +913,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
while (i != reactions.constEnd()) { while (i != reactions.constEnd()) {
QVariantList authors; QVariantList authors;
for (auto author : i.value()) { for (auto author : i.value()) {
authors.append(userAtEvent(author, m_currentRoom, evt)); authors.append(userInContext(author, m_currentRoom));
} }
bool hasLocalUser = i.value().contains(static_cast<NeoChatUser *>(m_currentRoom->localUser())); bool hasLocalUser = i.value().contains(static_cast<NeoChatUser *>(m_currentRoom->localUser()));
res.append(QVariantMap{{"reaction", i.key()}, {"count", i.value().count()}, {"authors", authors}, {"hasLocalUser", hasLocalUser}}); res.append(QVariantMap{{"reaction", i.key()}, {"count", i.value().count()}, {"authors", authors}, {"hasLocalUser", hasLocalUser}});
@@ -1088,3 +1100,23 @@ void MessageEventModel::loadReply(const QModelIndex &index)
Q_EMIT dataChanged(persistentIndex, persistentIndex, {ReplyRole}); Q_EMIT dataChanged(persistentIndex, persistentIndex, {ReplyRole});
}); });
} }
const RoomEvent *MessageEventModel::getReplyForEvent(const RoomEvent &event) const
{
const QString &replyEventId = event.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
if (replyEventId.isEmpty()) {
return {};
};
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
const RoomEvent *replyPtr = replyIt != m_currentRoom->historyEdge() ? &**replyIt : nullptr;
if (!replyPtr) {
for (const auto &e : m_extraEvents) {
if (e->id() == replyEventId) {
replyPtr = e.get();
break;
}
}
}
return replyPtr;
}

View File

@@ -75,6 +75,7 @@ public:
FileMimetypeIcon, /**< The icon name for the mime type of a file. */ FileMimetypeIcon, /**< The icon name for the mime type of a file. */
IsReplyRole, /**< Is the message a reply to another event. */ IsReplyRole, /**< Is the message a reply to another event. */
ReplyAuthor, /**< The author of the event that was replied to. */
ReplyRole, /**< The content data of the message that was replied to. */ ReplyRole, /**< The content data of the message that was replied to. */
ReplyIdRole, /**< The matrix ID of the message that was replied to. */ ReplyIdRole, /**< The matrix ID of the message that was replied to. */
@@ -192,6 +193,8 @@ private:
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {}); int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
void moveReadMarker(const QString &toEventId); void moveReadMarker(const QString &toEventId);
const Quotient::RoomEvent *getReplyForEvent(const Quotient::RoomEvent &event) const;
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents; std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
Q_SIGNALS: Q_SIGNALS:

View File

@@ -71,7 +71,8 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
return user->id(); return user->id();
} }
if (role == AvatarRole) { if (role == AvatarRole) {
return user->avatarMediaId(m_currentRoom); auto neoChatUser = static_cast<NeoChatUser *>(user);
return m_currentRoom->avatarForMember(neoChatUser);
} }
if (role == ObjectRole) { if (role == ObjectRole) {
return QVariant::fromValue(user); return QVariant::fromValue(user);

View File

@@ -1921,7 +1921,12 @@ QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType)
QUrl NeoChatRoom::avatarForMember(NeoChatUser *user) const QUrl NeoChatRoom::avatarForMember(NeoChatUser *user) const
{ {
#ifdef QUOTIENT_07 #ifdef QUOTIENT_07
return connection()->makeMediaUrl(memberAvatarUrl(user->id())); auto avatar = connection()->makeMediaUrl(memberAvatarUrl(user->id()));
if (avatar.isValid() && avatar.scheme() == QStringLiteral("mxc")) {
return avatar;
} else {
return QUrl();
}
#else #else
QUrl url(QStringLiteral("mxc://%1").arg(user->avatarMediaId())); QUrl url(QStringLiteral("mxc://%1").arg(user->avatarMediaId()));
QUrlQuery q(url.query()); QUrlQuery q(url.query());

View File

@@ -14,7 +14,7 @@ GridLayout {
id: root id: root
property string userName property string userName
property color userColor: Kirigami.Theme.highlightColor property color userColor: Kirigami.Theme.highlightColor
property var userAvatar: "" property url userAvatar: ""
property var text property var text
rows: 3 rows: 3

View File

@@ -35,7 +35,7 @@ Components.AlbumMaximizeComponent {
implicitHeight: Kirigami.Units.iconSizes.medium implicitHeight: Kirigami.Units.iconSizes.medium
name: modelData.author.name ?? modelData.author.displayName name: modelData.author.name ?? modelData.author.displayName
source: modelData.author.avatarMediaId ? ("image://mxc/" + modelData.author.avatarMediaId) : "" source: modelData.author.avatarSource
color: modelData.author.color color: modelData.author.color
} }
ColumnLayout { ColumnLayout {

View File

@@ -21,7 +21,7 @@ Flow {
implicitHeight: avatarSize implicitHeight: avatarSize
name: modelData.displayName name: modelData.displayName
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : "" source: modelData.avatarSource
color: modelData.color color: modelData.color
} }
} }

View File

@@ -71,7 +71,7 @@ TimelineContainer {
width: height width: height
height: parent.height / 3 + 1 height: parent.height / 3 + 1
name: model.author.name ?? model.author.displayName name: model.author.name ?? model.author.displayName
source: model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : "" source: model.author.avatarSource
color: model.author.color color: model.author.color
} }
} }

View File

@@ -82,7 +82,7 @@ QQC2.Control {
implicitHeight: Kirigami.Units.iconSizes.small implicitHeight: Kirigami.Units.iconSizes.small
name: modelData.displayName name: modelData.displayName
source: modelData.avatarMediaId ? ("image://mxc/" + modelData.avatarMediaId) : "" source: modelData.avatarSource
color: modelData.color color: modelData.color
} }
} }
@@ -121,7 +121,7 @@ QQC2.Control {
visible: !columnLayout.folded || stateEventRepeater.count === 1 visible: !columnLayout.folded || stateEventRepeater.count === 1
name: modelData.author.displayName name: modelData.author.displayName
avatar: modelData.author.avatarMediaId ? ("image://mxc/" + modelData.author.avatarMediaId) : "" avatar: modelData.author.avatarSource
color: modelData.author.color color: modelData.author.color
text: `<style>a {text-decoration: none;}</style><a href="https://matrix.to/#/${modelData.author.id}" style="color: ${modelData.author.color}">${modelData.authorDisplayName}</a> ${modelData.text}` text: `<style>a {text-decoration: none;}</style><a href="https://matrix.to/#/${modelData.author.id}" style="color: ${modelData.author.color}">${modelData.authorDisplayName}</a> ${modelData.text}`

View File

@@ -135,7 +135,7 @@ ColumnLayout {
Config.showAvatarInTimeline && Config.showAvatarInTimeline &&
(Config.compactLayout || !showUserMessageOnRight) (Config.compactLayout || !showUserMessageOnRight)
name: model.author.name ?? model.author.displayName name: model.author.name ?? model.author.displayName
source: visible && model.author.avatarMediaId ? ("image://mxc/" + model.author.avatarMediaId) : "" source: model.author.avatarSource
color: model.author.color color: model.author.color
MouseArea { MouseArea {
@@ -144,9 +144,7 @@ ColumnLayout {
userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, { userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {
room: currentRoom, room: currentRoom,
user: author.object, user: author.object,
displayName: author.displayName, displayName: author.displayName
avatarMediaId: author.avatarMediaId,
avatarUrl: author.avatarUrl
}).open(); }).open();
} }
cursorShape: Qt.PointingHandCursor cursorShape: Qt.PointingHandCursor
@@ -231,8 +229,7 @@ ColumnLayout {
room: currentRoom, room: currentRoom,
user: author.object, user: author.object,
displayName: author.displayName, displayName: author.displayName,
avatarMediaId: author.avatarMediaId, avatarSource: author.avatarSource
avatarUrl: author.avatarUrl
}).open(); }).open();
} }
} }
@@ -260,9 +257,9 @@ ColumnLayout {
visible: active visible: active
sourceComponent: ReplyComponent { sourceComponent: ReplyComponent {
name: currentRoom.htmlSafeMemberName(reply.author.id) name: currentRoom.htmlSafeMemberName(model.replyAuthor.id)
avatar: reply.author.avatarMediaId ? ("image://mxc/" + reply.author.avatarMediaId) : "" avatar: model.replyAuthor.avatarSource
color: reply.author.color color: model.replyAuthor.color
} }
Connections { Connections {

View File

@@ -19,7 +19,7 @@ Kirigami.OverlaySheet {
property var user property var user
property string displayName: user.displayName property string displayName: user.displayName
readonly property string avatar: room.avatarForMember(user) readonly property url avatar: room.avatarForMember(user)
parent: applicationWindow().overlay parent: applicationWindow().overlay
@@ -45,7 +45,7 @@ Kirigami.OverlaySheet {
Layout.preferredHeight: Kirigami.Units.iconSizes.huge Layout.preferredHeight: Kirigami.Units.iconSizes.huge
name: displayName name: displayName
source: avatar ?? "" source: avatar
color: user.color color: user.color
} }

View File

@@ -226,7 +226,7 @@ Loader {
spacing: Kirigami.Units.largeSpacing spacing: Kirigami.Units.largeSpacing
Kirigami.Avatar { Kirigami.Avatar {
id: avatar id: avatar
source: author.avatarMediaId ? ("image://mxc/" + author.avatarMediaId) : "" source: author.avatarSource
Layout.preferredWidth: Kirigami.Units.gridUnit * 3 Layout.preferredWidth: Kirigami.Units.gridUnit * 3
Layout.preferredHeight: Kirigami.Units.gridUnit * 3 Layout.preferredHeight: Kirigami.Units.gridUnit * 3
Layout.alignment: Qt.AlignTop Layout.alignment: Qt.AlignTop

View File

@@ -258,7 +258,7 @@ Kirigami.OverlayDrawer {
implicitWidth: height implicitWidth: height
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5 sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5 sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
source: avatar ? ("image://mxc/" + avatar) : "" source: avatar
name: model.userId name: model.userId
} }