diff --git a/autotests/CMakeLists.txt b/autotests/CMakeLists.txt index bea7e4fe5..1bd269158 100644 --- a/autotests/CMakeLists.txt +++ b/autotests/CMakeLists.txt @@ -82,3 +82,9 @@ ecm_add_test( LINK_LIBRARIES neochat Qt::Test TEST_NAME linkpreviewertest ) + +ecm_add_test( + messagecontentmodeltest.cpp + LINK_LIBRARIES neochat Qt::Test + TEST_NAME messagecontentmodeltest +) diff --git a/autotests/messagecontentmodeltest.cpp b/autotests/messagecontentmodeltest.cpp new file mode 100644 index 000000000..4d6309ccf --- /dev/null +++ b/autotests/messagecontentmodeltest.cpp @@ -0,0 +1,61 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL + +#include +#include +#include + +#include +#include +#include +#include + +#include "models/messagecontentmodel.h" + +#include "testutils.h" + +using namespace Quotient; +using namespace Qt::Literals::StringLiterals; + +class MessageContentModelTest : public QObject +{ + Q_OBJECT + +private: + Connection *connection = nullptr; + +private Q_SLOTS: + void initTestCase(); + + void missingEvent(); +}; + +void MessageContentModelTest::initTestCase() +{ + connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org")); +} + +void MessageContentModelTest::missingEvent() +{ + auto room = new TestUtils::TestRoom(connection, QStringLiteral("#firstRoom:kde.org")); + auto model1 = MessageContentModel(room, "$153456789:example.org"_L1); + + QCOMPARE(model1.rowCount(), 1); + QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading); + QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), "Loading"_L1); + + auto model2 = MessageContentModel(room, "$153456789:example.org"_L1, true); + + QCOMPARE(model2.rowCount(), 1); + QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading); + QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), "Loading reply"_L1); + + room->syncNewEvents(QLatin1String("test-min-sync.json")); + QCOMPARE(model1.rowCount(), 2); + QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Author); + QCOMPARE(model1.data(model1.index(1), MessageContentModel::ComponentTypeRole), MessageComponentType::Text); + QCOMPARE(model1.data(model1.index(1), MessageContentModel::DisplayRole), u"This is an example
text message
"_s); +} + +QTEST_MAIN(MessageContentModelTest) +#include "messagecontentmodeltest.moc" diff --git a/src/models/messagecontentmodel.cpp b/src/models/messagecontentmodel.cpp index 0009fdda7..33ed6d4df 100644 --- a/src/models/messagecontentmodel.cpp +++ b/src/models/messagecontentmodel.cpp @@ -29,18 +29,6 @@ using namespace Quotient; -MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply, bool isPending, MessageContentModel *parent) - : QAbstractListModel(parent) - , m_room(room) - , m_eventId(event != nullptr ? event->id() : QString()) - , m_eventSenderId(event != nullptr ? event->senderId() : QString()) - , m_isPending(isPending) - , m_isReply(isReply) -{ - intiializeEvent(event); - initializeModel(); -} - MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply, bool isPending, MessageContentModel *parent) : QAbstractListModel(parent) , m_room(room) @@ -54,17 +42,137 @@ MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &event void MessageContentModel::initializeModel() { Q_ASSERT(m_room != nullptr); - // Allow making a model for an event that is being downloaded but will appear later - // e.g. a reply, but we need an ID to know when it has arrived. - // Also note that a pending event may not have an event ID yet but as long as we have an event - // pointer we can pass out the transaction ID until it is set. - Q_ASSERT(!m_eventId.isEmpty() || m_event != nullptr); + Q_ASSERT(!m_eventId.isEmpty()); + connect(this, &MessageContentModel::eventUnavailable, this, &MessageContentModel::getEvent); + + connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) { + if (m_room != nullptr) { + if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) { + beginResetModel(); + m_isPending = false; + m_eventId = serverEvent->id(); + initializeEvent(); + endResetModel(); + } + } + }); + connect(m_room, &NeoChatRoom::addedMessages, this, [this](int fromIndex, int toIndex) { + if (m_room != nullptr) { + for (int i = fromIndex; i <= toIndex; i++) { + if (m_room->findInTimeline(i)->event()->id() == m_eventId) { + initializeEvent(); + updateReplyModel(); + resetModel(); + } + } + } + }); + connect(m_room, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) { + if (m_room != nullptr) { + if (m_eventId == newEvent->id()) { + beginResetModel(); + initializeEvent(); + resetContent(); + endResetModel(); + } + } + }); + connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) { + if (eventId == m_eventId) { + Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); + } + }); + connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) { + if (eventId == m_eventId) { + Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); + } + }); + connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) { + if (m_room != nullptr && eventId == m_eventId) { + resetContent(); + Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); + } + }); + connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) { + if (eventId == m_eventId) { + resetContent(); + Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); + } + }); + connect(m_room->editCache(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) { + if (oldEventId == m_eventId || newEventId == m_eventId) { + // HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate. + beginResetModel(); + resetContent(newEventId == m_eventId); + endResetModel(); + } + }); + connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) { + if (oldThreadId == m_eventId || newThreadId == m_eventId) { + beginResetModel(); + resetContent(false, newThreadId == m_eventId); + endResetModel(); + } + }); + connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() { + resetContent(); + }); + connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() { + resetContent(); + }); + connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) { + if (m_room != nullptr) { + if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) { + Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole}); + } + } + }); + connect(m_room, &Room::memberAvatarUpdated, this, [this](RoomMember member) { + if (m_room != nullptr) { + if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) { + Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole}); + } + } + }); + + connect(NeoChatConfig::self(), &NeoChatConfig::ThreadsChanged, this, [this]() { + updateReplyModel(); + resetModel(); + }); + + initializeEvent(); + updateReplyModel(); + resetModel(); +} + +void MessageContentModel::initializeEvent() +{ + const auto event = m_room->getEvent(m_eventId); + if (event == nullptr) { + Q_EMIT eventUnavailable(); + return; + } + + if (m_eventSenderObject == nullptr) { + auto senderId = event->senderId(); + // A pending event might not have a sender ID set yet but in that case it must + // be the local member. + if (senderId.isEmpty()) { + senderId = m_room->localMember().id(); + } + m_eventSenderObject = std::unique_ptr(new NeochatRoomMember(m_room, senderId)); + } + Q_EMIT eventUpdated(); +} + +void MessageContentModel::getEvent() +{ Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) { if (m_room != nullptr) { if (eventId == m_eventId) { m_notFound = false; - intiializeEvent(m_room->getEvent(eventId)); + initializeEvent(); updateReplyModel(); resetModel(); return true; @@ -83,130 +191,7 @@ void MessageContentModel::initializeModel() return false; }); - if (m_event == nullptr) { - intiializeEvent(m_room->getEvent(m_eventId)); - if (m_event == nullptr) { - m_room->downloadEventFromServer(m_eventId); - } - } - - connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) { - if (m_room != nullptr && m_event != nullptr) { - if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) { - beginResetModel(); - m_isPending = false; - intiializeEvent(serverEvent); - endResetModel(); - } - } - }); - connect(m_room, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) { - if (m_room != nullptr && m_event != nullptr) { - if (m_eventId == newEvent->id()) { - beginResetModel(); - intiializeEvent(newEvent); - resetContent(); - endResetModel(); - } - } - }); - connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) { - if (m_event != nullptr && eventId == m_eventId) { - Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); - } - }); - connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) { - if (m_event != nullptr && eventId == m_eventId) { - Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); - } - }); - connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) { - if (m_room != nullptr && m_event != nullptr && eventId == m_eventId) { - resetContent(); - Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); - } - }); - connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) { - if (m_event != nullptr && eventId == m_eventId) { - resetContent(); - Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole}); - } - }); - connect(m_room->editCache(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) { - if (m_event != nullptr && (oldEventId == m_eventId || newEventId == m_eventId)) { - // HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate. - beginResetModel(); - resetContent(newEventId == m_eventId); - endResetModel(); - } - }); - connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) { - if (m_event != nullptr && (oldThreadId == m_eventId || newThreadId == m_eventId)) { - beginResetModel(); - resetContent(false, newThreadId == m_eventId); - endResetModel(); - } - }); - connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() { - resetContent(); - }); - connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() { - resetContent(); - }); - connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) { - if (m_room != nullptr && m_event != nullptr) { - if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) { - Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole}); - } - } - }); - connect(m_room, &Room::memberAvatarUpdated, this, [this](RoomMember member) { - if (m_room != nullptr && m_event != nullptr) { - if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) { - Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole}); - } - } - }); - - connect(NeoChatConfig::self(), &NeoChatConfig::ThreadsChanged, this, [this]() { - updateReplyModel(); - resetModel(); - }); - - if (m_event != nullptr) { - updateReplyModel(); - } - resetModel(); -} - -void MessageContentModel::intiializeEvent(const QString &eventId) -{ - const auto newEvent = m_room->getEvent(eventId); - if (newEvent != nullptr) { - intiializeEvent(newEvent); - } -} - -void MessageContentModel::intiializeEvent(const Quotient::RoomEvent *event) -{ - if (event == nullptr) { - return; - } - - m_event = loadEvent(event->fullJson()); - // a pending event may not previously have had an event ID so update. - m_eventId = EventHandler::id(m_event.get()); - - auto senderId = m_event->senderId(); - // A pending event might not have a sender ID set yet but in that case it must - // be the local member. - if (senderId.isEmpty()) { - senderId = m_room->localMember().id(); - } - if (m_eventSenderObject == nullptr) { - m_eventSenderObject = std::unique_ptr(new NeochatRoomMember(m_room, senderId)); - } - Q_EMIT eventUpdated(); + m_room->downloadEventFromServer(m_eventId); } bool MessageContentModel::showAuthor() const @@ -221,7 +206,7 @@ void MessageContentModel::setShowAuthor(bool showAuthor) } m_showAuthor = showAuthor; - if (m_event != nullptr && m_room->connection()->isIgnored(m_event->senderId())) { + if (m_room->connection()->isIgnored(m_eventSenderId)) { if (showAuthor) { beginInsertRows({}, 0, 0); m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}}); @@ -250,8 +235,23 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const const auto component = m_components[index.row()]; + const auto event = m_room->getEvent(m_eventId); + if (event == nullptr) { + if (role == DisplayRole) { + if (m_isReply) { + return i18n("Loading reply"); + } else { + return i18n("Loading"); + } + } + if (role == ComponentTypeRole) { + return component.type; + } + return {}; + } + if (role == DisplayRole) { - if (m_notFound || (m_event && m_room->connection()->isIgnored(m_event->senderId()))) { + if (m_notFound || m_room->connection()->isIgnored(m_eventSenderId)) { Kirigami::Platform::PlatformTheme *theme = static_cast(qmlAttachedPropertiesObject(this, true)); @@ -265,16 +265,17 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const + i18nc("@info", "This message was either not found, you do not have permission to view it, or it was sent by an ignored user") + QStringLiteral("")); } - if (component.type == MessageComponentType::Loading && m_isReply) { - return i18n("Loading reply"); - } - if (m_event == nullptr) { - return QString(); + if (component.type == MessageComponentType::Loading) { + if (m_isReply) { + return i18n("Loading reply"); + } else { + return i18n("Loading"); + } } if (!component.content.isEmpty()) { return component.content; } - return EventHandler::richBody(m_room, m_event.get()); + return EventHandler::richBody(m_room, event); } if (role == ComponentTypeRole) { return component.type; @@ -283,53 +284,53 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const return component.attributes; } if (role == EventIdRole) { - return EventHandler::id(m_event.get()); + return EventHandler::id(event); } if (role == TimeRole) { - const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) { - return m_event->transactionId() == pendingEvent->transactionId(); + const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) { + return event->transactionId() == pendingEvent->transactionId(); }); auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated(); - return EventHandler::time(m_event.get(), m_isPending, lastUpdated); + return EventHandler::time(event, m_isPending, lastUpdated); } if (role == TimeStringRole) { - const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) { - return m_event->transactionId() == pendingEvent->transactionId(); + const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [event](const PendingEventItem &pendingEvent) { + return event->transactionId() == pendingEvent->transactionId(); }); auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated(); - return EventHandler::timeString(m_event.get(), QStringLiteral("hh:mm"), m_isPending, lastUpdated); + return EventHandler::timeString(event, QStringLiteral("hh:mm"), m_isPending, lastUpdated); } if (role == AuthorRole) { return QVariant::fromValue(m_eventSenderObject.get()); } if (role == MediaInfoRole) { - return EventHandler::mediaInfo(m_room, m_event.get()); + return EventHandler::mediaInfo(m_room, event); } if (role == FileTransferInfoRole) { - return QVariant::fromValue(m_room->cachedFileTransferInfo(m_event.get())); + return QVariant::fromValue(m_room->cachedFileTransferInfo(event)); } if (role == ItineraryModelRole) { return QVariant::fromValue(m_itineraryModel); } if (role == LatitudeRole) { - return EventHandler::latitude(m_event.get()); + return EventHandler::latitude(event); } if (role == LongitudeRole) { - return EventHandler::longitude(m_event.get()); + return EventHandler::longitude(event); } if (role == AssetRole) { - return EventHandler::locationAssetType(m_event.get()); + return EventHandler::locationAssetType(event); } if (role == PollHandlerRole) { return QVariant::fromValue(m_room->poll(m_eventId)); } if (role == ReplyEventIdRole) { - return EventHandler::replyId(m_event.get()); + return EventHandler::replyId(event); } if (role == ReplyAuthorRole) { - return QVariant::fromValue(EventHandler::replyAuthor(m_room, m_event.get())); + return QVariant::fromValue(EventHandler::replyAuthor(m_room, event)); } if (role == ReplyContentModelRole) { return QVariant::fromValue(m_replyModel); @@ -385,16 +386,18 @@ QHash MessageContentModel::roleNames() const void MessageContentModel::resetModel() { + const auto event = m_room->getEvent(m_eventId); + beginResetModel(); m_components.clear(); - if ((m_event && m_room->connection()->isIgnored(m_event->senderId())) || m_notFound) { + if (m_room->connection()->isIgnored(m_eventSenderId) || m_notFound) { m_components += MessageComponent{MessageComponentType::Text, QString(), {}}; endResetModel(); return; } - if (m_event == nullptr) { + if (event == nullptr) { m_components += MessageComponent{MessageComponentType::Loading, QString(), {}}; endResetModel(); return; @@ -410,8 +413,6 @@ void MessageContentModel::resetModel() void MessageContentModel::resetContent(bool isEditing, bool isThreading) { - Q_ASSERT(m_event != nullptr); - const auto startRow = m_components[0].type == MessageComponentType::Author ? 1 : 0; beginRemoveRows({}, startRow, rowCount() - 1); m_components.remove(startRow, rowCount() - startRow); @@ -428,15 +429,20 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading) QList MessageContentModel::messageContentComponents(bool isEditing, bool isThreading) { + const auto event = m_room->getEvent(m_eventId); + if (event == nullptr) { + return {}; + } + QList newComponents; - if (eventCast(m_event) - && eventCast(m_event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) { + if (eventCast(event) + && eventCast(event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) { newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}}; return newComponents; } - if (m_event->isRedacted()) { + if (event->isRedacted()) { newComponents += MessageComponent{MessageComponentType::Text, QString(), {}}; return newComponents; } @@ -448,7 +454,7 @@ QList MessageContentModel::messageContentComponents(bool isEdi if (isEditing) { newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}}; } else { - newComponents.append(componentsForType(MessageComponentType::typeForEvent(*m_event.get()))); + newComponents.append(componentsForType(MessageComponentType::typeForEvent(*event))); } if (m_room->urlPreviewEnabled()) { @@ -456,7 +462,7 @@ QList MessageContentModel::messageContentComponents(bool isEdi } // If the event is already threaded the ThreadModel will handle displaying a chat bar. - if (isThreading && !EventHandler::isThreaded(m_event.get())) { + if (isThreading && !EventHandler::isThreaded(event)) { newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}}; } @@ -465,11 +471,12 @@ QList MessageContentModel::messageContentComponents(bool isEdi void MessageContentModel::updateReplyModel() { - if (m_event == nullptr || m_isReply) { + const auto event = m_room->getEvent(m_eventId); + if (event == nullptr || m_isReply) { return; } - if (!EventHandler::hasReply(m_event.get()) || (EventHandler::isThreaded(m_event.get()) && NeoChatConfig::self()->threads())) { + if (!EventHandler::hasReply(event) || (EventHandler::isThreaded(event) && NeoChatConfig::self()->threads())) { if (m_replyModel) { delete m_replyModel; } @@ -480,12 +487,7 @@ void MessageContentModel::updateReplyModel() return; } - const auto replyEvent = m_room->findInTimeline(EventHandler::replyId(m_event.get())); - if (replyEvent == m_room->historyEdge()) { - m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(m_event.get()), true, false, this); - } else { - m_replyModel = new MessageContentModel(m_room, replyEvent->get(), true, false, this); - } + m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(event), true, false, this); connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() { Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole}); @@ -494,28 +496,37 @@ void MessageContentModel::updateReplyModel() QList MessageContentModel::componentsForType(MessageComponentType::Type type) { + const auto event = m_room->getEvent(m_eventId); + if (event == nullptr) { + return {}; + } + switch (type) { case MessageComponentType::Text: { - const auto event = eventCast(m_event); - auto body = EventHandler::rawMessageBody(*event); - return TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced()); + const auto roomMessageEvent = eventCast(event); + auto body = EventHandler::rawMessageBody(*roomMessageEvent); + return TextHandler().textComponents(body, + EventHandler::messageBodyInputFormat(*roomMessageEvent), + m_room, + roomMessageEvent, + roomMessageEvent->isReplaced()); } case MessageComponentType::File: { QList components; components += MessageComponent{MessageComponentType::File, QString(), {}}; - const auto event = eventCast(m_event); + const auto roomMessageEvent = eventCast(event); if (m_emptyItinerary) { if (!m_isReply) { - auto fileTransferInfo = m_room->cachedFileTransferInfo(m_event.get()); + auto fileTransferInfo = m_room->cachedFileTransferInfo(event); #ifndef Q_OS_ANDROID - Q_ASSERT(event->content() != nullptr && event->content()->fileInfo() != nullptr); - const QMimeType mimeType = event->content()->fileInfo()->mimeType; + Q_ASSERT(roomMessageEvent->content() != nullptr && roomMessageEvent->content()->fileInfo() != nullptr); + const QMimeType mimeType = roomMessageEvent->content()->fileInfo()->mimeType; if (mimeType.name() == QStringLiteral("text/plain") || mimeType.parentMimeTypes().contains(QStringLiteral("text/plain"))) { - QString originalName = event->content()->fileInfo()->originalName; + QString originalName = roomMessageEvent->content()->fileInfo()->originalName; if (originalName.isEmpty()) { - originalName = event->plainBody(); + originalName = roomMessageEvent->plainBody(); } KSyntaxHighlighting::Repository repository; KSyntaxHighlighting::Definition definitionForFile = repository.definitionForFileName(originalName); @@ -544,19 +555,27 @@ QList MessageContentModel::componentsForType(MessageComponentT } else { updateItineraryModel(); } - auto body = EventHandler::rawMessageBody(*event); - components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced()); + auto body = EventHandler::rawMessageBody(*roomMessageEvent); + components += TextHandler().textComponents(body, + EventHandler::messageBodyInputFormat(*roomMessageEvent), + m_room, + roomMessageEvent, + roomMessageEvent->isReplaced()); return components; } case MessageComponentType::Image: case MessageComponentType::Audio: case MessageComponentType::Video: { - if (!m_event->is()) { - const auto event = eventCast(m_event); + if (!event->is()) { + const auto roomMessageEvent = eventCast(event); QList components; components += MessageComponent{type, QString(), {}}; - auto body = EventHandler::rawMessageBody(*event); - components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced()); + auto body = EventHandler::rawMessageBody(*roomMessageEvent); + components += TextHandler().textComponents(body, + EventHandler::messageBodyInputFormat(*roomMessageEvent), + m_room, + roomMessageEvent, + roomMessageEvent->isReplaced()); return components; } } @@ -626,13 +645,14 @@ void MessageContentModel::closeLinkPreview(int row) void MessageContentModel::updateItineraryModel() { - if (m_room == nullptr || m_event == nullptr) { + const auto event = m_room->getEvent(m_eventId); + if (m_room == nullptr || event == nullptr) { return; } - if (auto event = eventCast(m_event)) { - if (event->hasFileContent()) { - auto filePath = m_room->cachedFileTransferInfo(m_event.get()).localPath; + if (auto roomMessageEvent = eventCast(event)) { + if (roomMessageEvent->hasFileContent()) { + auto filePath = m_room->cachedFileTransferInfo(event).localPath; if (filePath.isEmpty() && m_itineraryModel != nullptr) { delete m_itineraryModel; m_itineraryModel = nullptr; diff --git a/src/models/messagecontentmodel.h b/src/models/messagecontentmodel.h index 2bc8af975..6c005e63d 100644 --- a/src/models/messagecontentmodel.h +++ b/src/models/messagecontentmodel.h @@ -75,11 +75,10 @@ public: Q_ENUM(Roles) explicit MessageContentModel(NeoChatRoom *room, - const Quotient::RoomEvent *event, + const QString &eventId, bool isReply = false, bool isPending = false, MessageContentModel *parent = nullptr); - MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false, bool isPending = false, MessageContentModel *parent = nullptr); bool showAuthor() const; void setShowAuthor(bool showAuthor); @@ -114,6 +113,7 @@ public: Q_SIGNALS: void showAuthorChanged(); + void eventUnavailable(); void eventUpdated(); private: @@ -121,7 +121,6 @@ private: QString m_eventId; QString m_eventSenderId; std::unique_ptr m_eventSenderObject = nullptr; - Quotient::RoomEventPtr m_event; bool m_isPending; bool m_showAuthor = true; @@ -129,8 +128,8 @@ private: bool m_notFound = false; void initializeModel(); - void intiializeEvent(const QString &eventId); - void intiializeEvent(const Quotient::RoomEvent *event); + void initializeEvent(); + void getEvent(); QList m_components; void resetModel(); diff --git a/src/models/messageeventmodel.cpp b/src/models/messageeventmodel.cpp index 2d568e3ca..a08921e06 100644 --- a/src/models/messageeventmodel.cpp +++ b/src/models/messageeventmodel.cpp @@ -639,7 +639,7 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event) if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) { if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) { - m_contentModels[eventId] = std::unique_ptr(new MessageContentModel(m_currentRoom, event)); + m_contentModels[eventId] = std::unique_ptr(new MessageContentModel(m_currentRoom, eventId)); } } diff --git a/src/models/searchmodel.cpp b/src/models/searchmodel.cpp index 4960ce424..faa15e7c4 100644 --- a/src/models/searchmodel.cpp +++ b/src/models/searchmodel.cpp @@ -115,11 +115,11 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const return EventHandler::threadRoot(&event); case ContentModelRole: { if (!event.isStateEvent()) { - return QVariant::fromValue(new MessageContentModel(m_room, &event)); + return QVariant::fromValue(new MessageContentModel(m_room, event.id())); } if (event.isStateEvent()) { if (event.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) { - return QVariant::fromValue(new MessageContentModel(m_room, &event)); + return QVariant::fromValue(new MessageContentModel(m_room, event.id())); } } return {}; diff --git a/src/models/threadmodel.cpp b/src/models/threadmodel.cpp index a5aa2e553..52c703509 100644 --- a/src/models/threadmodel.cpp +++ b/src/models/threadmodel.cpp @@ -80,7 +80,7 @@ void ThreadModel::fetchMore(const QModelIndex &parent) const auto room = dynamic_cast(QObject::parent()); auto newEvents = m_currentJob->chunk(); for (auto &event : newEvents) { - m_contentModels.push_back(new MessageContentModel(room, event.get())); + m_contentModels.push_back(new MessageContentModel(room, event->id())); } addModels(); @@ -103,7 +103,7 @@ void ThreadModel::fetchMore(const QModelIndex &parent) void ThreadModel::addNewEvent(const Quotient::RoomEvent *event) { const auto room = dynamic_cast(QObject::parent()); - m_contentModels.push_front(new MessageContentModel(room, event)); + m_contentModels.push_front(new MessageContentModel(room, event->id())); } void ThreadModel::addModels() diff --git a/src/neochatroom.cpp b/src/neochatroom.cpp index 0d5d60158..74e14a155 100644 --- a/src/neochatroom.cpp +++ b/src/neochatroom.cpp @@ -1768,6 +1768,9 @@ QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType) void NeoChatRoom::downloadEventFromServer(const QString &eventId) { if (findInTimeline(eventId) != historyEdge()) { + // For whatever reason the event has now appeared so the function that called + // this need to whatever it wanted to do with the event. + Q_EMIT extraEventLoaded(eventId); return; } auto job = connection()->callApi(id(), eventId);