Files
neochat/src/app/models/notificationsmodel.cpp
Joshua Goins a1513b30cd Check if RoomEvent is null before trying to insert a notification
As seen in the bug report, roomEvent could be null and then we get a
nice and simple null-access error. We should be careful before trying to
use it.

BUG: 502687
2025-05-15 13:39:33 -04:00

169 lines
5.0 KiB
C++

// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "notificationsmodel.h"
#include <Quotient/events/event.h>
#include <Quotient/uri.h>
#include "eventhandler.h"
#include "neochatroom.h"
using namespace Quotient;
NotificationsModel::NotificationsModel(QObject *parent)
: QAbstractListModel(parent)
{
}
int NotificationsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_notifications.count();
}
QVariant NotificationsModel::data(const QModelIndex &index, int role) const
{
auto row = index.row();
if (row < 0 || row >= m_notifications.count()) {
return {};
}
if (role == TextRole) {
return m_notifications[row].text;
}
if (role == RoomIdRole) {
return m_notifications[row].roomId;
}
if (role == AuthorName) {
return m_notifications[row].authorName;
}
if (role == AuthorAvatar) {
return m_notifications[row].authorAvatar;
}
if (role == RoomRole) {
return QVariant::fromValue(m_connection->room(m_notifications[row].roomId));
}
if (role == EventIdRole) {
return m_notifications[row].eventId;
}
if (role == RoomDisplayNameRole) {
return m_notifications[row].roomDisplayName;
}
if (role == UriRole) {
return Uri(m_notifications[row].roomId.toLatin1(), m_notifications[row].eventId.toLatin1()).toUrl();
}
return {};
}
QHash<int, QByteArray> NotificationsModel::roleNames() const
{
return {
{TextRole, "text"},
{RoomIdRole, "roomId"},
{AuthorName, "authorName"},
{AuthorAvatar, "authorAvatar"},
{RoomRole, "room"},
{EventIdRole, "eventId"},
{RoomDisplayNameRole, "roomDisplayName"},
{UriRole, "uri"},
};
}
NeoChatConnection *NotificationsModel::connection() const
{
return m_connection;
}
void NotificationsModel::setConnection(NeoChatConnection *connection)
{
if (m_connection) {
// disconnect things...
}
if (!connection) {
return;
}
m_connection = connection;
Q_EMIT connectionChanged();
connect(connection, &Connection::syncDone, this, [this]() {
loadData();
});
loadData();
}
void NotificationsModel::loadData()
{
Q_ASSERT(m_connection);
if (m_job || (m_notifications.size() && m_nextToken.isEmpty())) {
return;
}
m_job = m_connection->callApi<GetNotificationsJob>(m_nextToken);
Q_EMIT loadingChanged();
connect(m_job, &BaseJob::finished, this, [this]() {
m_nextToken = m_job->nextToken();
Q_EMIT nextTokenChanged();
for (const auto &notification : m_job->notifications()) {
if (std::any_of(notification.actions.constBegin(), notification.actions.constEnd(), [](const QVariant &it) {
if (it.canConvert<QVariantMap>()) {
auto map = it.toMap();
if (map["set_tweak"_L1] == "highlight"_L1) {
return true;
}
}
return false;
})) {
const auto &authorId = notification.event->fullJson()["sender"_L1].toString();
const auto &room = m_connection->room(notification.roomId);
if (!room) {
continue;
}
auto u = room->member(authorId).avatarUrl();
auto avatar = u.isEmpty() ? QUrl() : connection()->makeMediaUrl(u);
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == u"mxc"_s ? avatar : QUrl();
const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get());
if (!roomEvent) {
continue;
}
beginInsertRows({}, m_notifications.length(), m_notifications.length());
m_notifications += Notification{
.roomId = notification.roomId,
.text = room->member(authorId).htmlSafeDisplayName() + (roomEvent->is<StateEvent>() ? u" "_s : u": "_s)
+ EventHandler::plainBody(dynamic_cast<NeoChatRoom *>(room), roomEvent, true),
.authorName = room->member(authorId).htmlSafeDisplayName(),
.authorAvatar = authorAvatar,
.eventId = roomEvent->id(),
.roomDisplayName = room->displayName(),
};
endInsertRows();
}
}
m_job = nullptr;
Q_EMIT loadingChanged();
});
}
bool NotificationsModel::canFetchMore(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return !m_nextToken.isEmpty();
}
void NotificationsModel::fetchMore(const QModelIndex &parent)
{
Q_UNUSED(parent);
loadData();
}
bool NotificationsModel::loading() const
{
return m_job;
}
QString NotificationsModel::nextToken() const
{
return m_nextToken;
}
#include "moc_notificationsmodel.cpp"