Refactor reactions
Currently we effectively create the reactions list in EventHandler then pass that data into a model. This reworks the model so that we just pass in a room and an event and it grabs it's own data. This means that: - the functions in event handler are no longer required - the model can update itself to add/remove reactions so no need to handle that in MessageEventModel - MessageEventModel only needs to create new ReactionModels or remove old ones when no reactions exist anymore A basic test suite has also been created for the ReactionModel
This commit is contained in:
@@ -72,6 +72,12 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
MessageEventModel::MessageEventModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(this, &MessageEventModel::modelAboutToBeReset, this, [this]() {
|
||||
resetting = true;
|
||||
});
|
||||
connect(this, &MessageEventModel::modelReset, this, [this]() {
|
||||
resetting = false;
|
||||
});
|
||||
}
|
||||
|
||||
NeoChatRoom *MessageEventModel::room() const
|
||||
@@ -245,7 +251,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(eventIt->event()));
|
||||
}
|
||||
}
|
||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole, Qt::DisplayRole});
|
||||
refreshEventRoles(eventId, {Qt::DisplayRole});
|
||||
});
|
||||
connect(m_currentRoom, &Room::changed, this, [this]() {
|
||||
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||
@@ -723,10 +729,28 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
|
||||
} else {
|
||||
m_linkPreviewers.remove(eventId);
|
||||
}
|
||||
if (auto reactionModel = eventHandler.getReactions()) {
|
||||
m_reactionModels[eventId] = reactionModel;
|
||||
|
||||
// ReactionModel handles updates to add and remove reactions, we only need to
|
||||
// handle adding and removing whole models here.
|
||||
if (m_reactionModels.contains(eventId)) {
|
||||
// If a model already exists but now has no reactions remove it
|
||||
if (m_reactionModels[eventId]->rowCount() <= 0) {
|
||||
m_reactionModels.remove(eventId);
|
||||
if (!resetting) {
|
||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_reactionModels.remove(eventId);
|
||||
if (m_currentRoom->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
|
||||
// If a model doesn't exist and there are reactions add it.
|
||||
auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(event, m_currentRoom));
|
||||
if (reactionModel->rowCount() > 0) {
|
||||
m_reactionModels[eventId] = reactionModel;
|
||||
if (!resetting) {
|
||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -131,6 +131,7 @@ private:
|
||||
QString lastReadEventId;
|
||||
QPersistentModelIndex m_lastReadEventIndex;
|
||||
int rowBelowInserted = -1;
|
||||
bool resetting = false;
|
||||
bool movingEvent = false;
|
||||
KFormat m_format;
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "reactionmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <QDebug>
|
||||
#ifdef HAVE_ICU
|
||||
@@ -15,11 +16,20 @@
|
||||
|
||||
#include <Quotient/user.h>
|
||||
|
||||
ReactionModel::ReactionModel(QObject *parent, QList<Reaction> reactions, Quotient::User *localUser)
|
||||
: QAbstractListModel(parent)
|
||||
, m_localUser(localUser)
|
||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, const NeoChatRoom *room)
|
||||
: QAbstractListModel(nullptr)
|
||||
, m_room(room)
|
||||
, m_event(event)
|
||||
{
|
||||
setReactions(reactions);
|
||||
if (m_event != nullptr && m_room != nullptr) {
|
||||
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
|
||||
if (m_event->id() == eventId) {
|
||||
updateReactions();
|
||||
}
|
||||
});
|
||||
|
||||
updateReactions();
|
||||
}
|
||||
}
|
||||
|
||||
QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
@@ -35,38 +45,11 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
const auto &reaction = m_reactions.at(index.row());
|
||||
|
||||
const auto isEmoji = [](const QString &text) {
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
int from = 0;
|
||||
while (finder.toNextBoundary() != -1) {
|
||||
auto to = finder.position();
|
||||
if (text[from].isSpace()) {
|
||||
from = to;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||
if (!u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION)) {
|
||||
return false;
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
};
|
||||
|
||||
const auto reactionText = isEmoji(reaction.reaction)
|
||||
? QStringLiteral("<span style=\"font-family: 'emoji';\">") + reaction.reaction + QStringLiteral("</span>")
|
||||
: reaction.reaction;
|
||||
|
||||
if (role == TextContentRole) {
|
||||
if (reaction.authors.count() > 1) {
|
||||
return QStringLiteral("%1 %2").arg(reactionText, QString::number(reaction.authors.count()));
|
||||
return QStringLiteral("%1 %2").arg(reactionText(reaction.reaction), QString::number(reaction.authors.count()));
|
||||
} else {
|
||||
return reactionText;
|
||||
return reactionText(reaction.reaction);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -97,7 +80,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
"%2 reacted with %3",
|
||||
reaction.authors.count(),
|
||||
text,
|
||||
reactionText);
|
||||
reactionText(reaction.reaction));
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -107,7 +90,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
if (role == HasLocalUser) {
|
||||
for (auto author : reaction.authors) {
|
||||
if (author.toMap()[QStringLiteral("id")] == m_localUser->id()) {
|
||||
if (author.toMap()[QStringLiteral("id")] == m_room->localUser()->id()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -123,11 +106,44 @@ int ReactionModel::rowCount(const QModelIndex &parent) const
|
||||
return m_reactions.count();
|
||||
}
|
||||
|
||||
void ReactionModel::setReactions(QList<Reaction> reactions)
|
||||
void ReactionModel::updateReactions()
|
||||
{
|
||||
beginResetModel();
|
||||
|
||||
m_reactions.clear();
|
||||
m_reactions = reactions;
|
||||
|
||||
const auto &annotations = m_room->relatedEvents(*m_event, Quotient::EventRelation::AnnotationType);
|
||||
if (annotations.isEmpty()) {
|
||||
endResetModel();
|
||||
return;
|
||||
};
|
||||
|
||||
QMap<QString, QList<Quotient::User *>> reactions = {};
|
||||
for (const auto &a : annotations) {
|
||||
if (a->isRedacted()) { // Just in case?
|
||||
continue;
|
||||
}
|
||||
if (const auto &e = eventCast<const Quotient::ReactionEvent>(a)) {
|
||||
reactions[e->key()].append(m_room->user(e->senderId()));
|
||||
}
|
||||
}
|
||||
|
||||
if (reactions.isEmpty()) {
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
auto i = reactions.constBegin();
|
||||
while (i != reactions.constEnd()) {
|
||||
QVariantList authors;
|
||||
for (const auto &author : i.value()) {
|
||||
authors.append(m_room->getUser(author));
|
||||
}
|
||||
|
||||
m_reactions.append(ReactionModel::Reaction{i.key(), authors});
|
||||
++i;
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
@@ -142,4 +158,32 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
QString ReactionModel::reactionText(const QString &text)
|
||||
{
|
||||
const auto isEmoji = [](const QString &text) {
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
int from = 0;
|
||||
while (finder.toNextBoundary() != -1) {
|
||||
auto to = finder.position();
|
||||
if (text[from].isSpace()) {
|
||||
from = to;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||
if (!u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION)) {
|
||||
return false;
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
};
|
||||
|
||||
return isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
}
|
||||
|
||||
#include "moc_reactionmodel.cpp"
|
||||
|
||||
@@ -3,8 +3,10 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "neochatroom.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
#include <Quotient/events/reactionevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
@@ -41,7 +43,7 @@ public:
|
||||
HasLocalUser, /**< Whether the local user is in the list of authors. */
|
||||
};
|
||||
|
||||
explicit ReactionModel(QObject *parent = nullptr, QList<Reaction> reactions = {}, Quotient::User *localUser = nullptr);
|
||||
explicit ReactionModel(const Quotient::RoomMessageEvent *event, const NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
@@ -64,14 +66,12 @@ public:
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Set the reactions data in the model.
|
||||
*/
|
||||
void setReactions(QList<Reaction> reactions);
|
||||
|
||||
private:
|
||||
const NeoChatRoom *m_room;
|
||||
const Quotient::RoomMessageEvent *m_event;
|
||||
QList<Reaction> m_reactions;
|
||||
|
||||
Quotient::User *m_localUser;
|
||||
void updateReactions();
|
||||
static QString reactionText(const QString &text);
|
||||
};
|
||||
Q_DECLARE_METATYPE(ReactionModel *)
|
||||
|
||||
Reference in New Issue
Block a user