Reaction Model
Create a reaction model that provides all the required data for `ReactionDelegate` so that none need to be calculated in QML. This also cleans up the API for `ReactionDelegate`
This commit is contained in:
@@ -54,6 +54,7 @@ add_library(neochat STATIC
|
|||||||
events/imagepackevent.cpp
|
events/imagepackevent.cpp
|
||||||
events/joinrulesevent.cpp
|
events/joinrulesevent.cpp
|
||||||
events/stickerevent.cpp
|
events/stickerevent.cpp
|
||||||
|
models/reactionmodel.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
ecm_qt_declare_logging_category(neochat
|
ecm_qt_declare_logging_category(neochat
|
||||||
|
|||||||
@@ -59,6 +59,7 @@
|
|||||||
#include "models/messageeventmodel.h"
|
#include "models/messageeventmodel.h"
|
||||||
#include "models/messagefiltermodel.h"
|
#include "models/messagefiltermodel.h"
|
||||||
#include "models/publicroomlistmodel.h"
|
#include "models/publicroomlistmodel.h"
|
||||||
|
#include "models/reactionmodel.h"
|
||||||
#include "models/roomlistmodel.h"
|
#include "models/roomlistmodel.h"
|
||||||
#include "models/searchmodel.h"
|
#include "models/searchmodel.h"
|
||||||
#include "models/serverlistmodel.h"
|
#include "models/serverlistmodel.h"
|
||||||
@@ -230,6 +231,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<KWebShortcutModel>("org.kde.neochat", 1, 0, "WebShortcutModel");
|
qmlRegisterType<KWebShortcutModel>("org.kde.neochat", 1, 0, "WebShortcutModel");
|
||||||
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
qmlRegisterType<UserListModel>("org.kde.neochat", 1, 0, "UserListModel");
|
||||||
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
||||||
|
qmlRegisterType<ReactionModel>("org.kde.neochat", 1, 0, "ReactionModel");
|
||||||
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
|
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
|
||||||
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
||||||
qmlRegisterType<UserFilterModel>("org.kde.neochat", 1, 0, "UserFilterModel");
|
qmlRegisterType<UserFilterModel>("org.kde.neochat", 1, 0, "UserFilterModel");
|
||||||
|
|||||||
@@ -27,6 +27,7 @@
|
|||||||
#include <KFormat>
|
#include <KFormat>
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
#include "models/reactionmodel.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "texthandler.h"
|
#include "texthandler.h"
|
||||||
|
|
||||||
@@ -61,6 +62,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[ReadMarkersStringRole] = "readMarkersString";
|
roles[ReadMarkersStringRole] = "readMarkersString";
|
||||||
roles[ShowReadMarkersRole] = "showReadMarkers";
|
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||||
roles[ReactionRole] = "reaction";
|
roles[ReactionRole] = "reaction";
|
||||||
|
roles[ShowReactionsRole] = "showReactions";
|
||||||
roles[SourceRole] = "source";
|
roles[SourceRole] = "source";
|
||||||
roles[MimeTypeRole] = "mimeType";
|
roles[MimeTypeRole] = "mimeType";
|
||||||
roles[FormattedBodyRole] = "formattedBody";
|
roles[FormattedBodyRole] = "formattedBody";
|
||||||
@@ -106,6 +108,8 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
if (m_currentRoom) {
|
if (m_currentRoom) {
|
||||||
m_currentRoom->disconnect(this);
|
m_currentRoom->disconnect(this);
|
||||||
m_linkPreviewers.clear();
|
m_linkPreviewers.clear();
|
||||||
|
qDeleteAll(m_reactionModels);
|
||||||
|
m_reactionModels.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_currentRoom = room;
|
m_currentRoom = room;
|
||||||
@@ -116,6 +120,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
|
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
|
||||||
if (auto e = &*event->viewAs<RoomMessageEvent>()) {
|
if (auto e = &*event->viewAs<RoomMessageEvent>()) {
|
||||||
createLinkPreviewerForEvent(e);
|
createLinkPreviewerForEvent(e);
|
||||||
|
createReactionModelForEvent(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,6 +139,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||||
if (message != nullptr) {
|
if (message != nullptr) {
|
||||||
createLinkPreviewerForEvent(message);
|
createLinkPreviewerForEvent(message);
|
||||||
|
createReactionModelForEvent(message);
|
||||||
|
|
||||||
if (NeoChatConfig::self()->showFancyEffects()) {
|
if (NeoChatConfig::self()->showFancyEffects()) {
|
||||||
QString planBody = message->plainBody();
|
QString planBody = message->plainBody();
|
||||||
@@ -173,6 +179,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||||
if (message) {
|
if (message) {
|
||||||
createLinkPreviewerForEvent(message);
|
createLinkPreviewerForEvent(message);
|
||||||
|
createReactionModelForEvent(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (rowCount() > 0) {
|
if (rowCount() > 0) {
|
||||||
@@ -244,7 +251,11 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
if (eventId.isEmpty()) { // How did we get here?
|
if (eventId.isEmpty()) { // How did we get here?
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
refreshEventRoles(eventId, {ReactionRole, Qt::DisplayRole});
|
const auto eventIt = m_currentRoom->findInTimeline(eventId);
|
||||||
|
if (eventIt != m_currentRoom->historyEdge()) {
|
||||||
|
createReactionModelForEvent(static_cast<const RoomMessageEvent *>(&**eventIt));
|
||||||
|
}
|
||||||
|
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole, Qt::DisplayRole});
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::changed, this, [this]() {
|
connect(m_currentRoom, &Room::changed, this, [this]() {
|
||||||
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||||
@@ -932,38 +943,17 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == ReactionRole) {
|
if (role == ReactionRole) {
|
||||||
const auto &annotations = m_currentRoom->relatedEvents(evt, EventRelation::Annotation());
|
if (m_reactionModels.contains(evt.id())) {
|
||||||
if (annotations.isEmpty()) {
|
return QVariant::fromValue<ReactionModel *>(m_reactionModels[evt.id()]);
|
||||||
return {};
|
} else {
|
||||||
};
|
return QVariantList();
|
||||||
QMap<QString, QList<NeoChatUser *>> reactions = {};
|
|
||||||
for (const auto &a : annotations) {
|
|
||||||
if (a->isRedacted()) { // Just in case?
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (auto e = eventCast<const ReactionEvent>(a)) {
|
|
||||||
reactions[e->relation().key].append(static_cast<NeoChatUser *>(m_currentRoom->user(e->senderId())));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reactions.isEmpty()) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList res = {};
|
|
||||||
auto i = reactions.constBegin();
|
|
||||||
while (i != reactions.constEnd()) {
|
|
||||||
QVariantList authors;
|
|
||||||
for (auto author : i.value()) {
|
|
||||||
authors.append(userInContext(author, m_currentRoom));
|
|
||||||
}
|
|
||||||
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}});
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role == ShowReactionsRole) {
|
||||||
|
return m_reactionModels.contains(evt.id());
|
||||||
|
}
|
||||||
|
|
||||||
if (role == AuthorIdRole) {
|
if (role == AuthorIdRole) {
|
||||||
return evt.senderId();
|
return evt.senderId();
|
||||||
}
|
}
|
||||||
@@ -1278,3 +1268,60 @@ void MessageEventModel::createLinkPreviewerForEvent(const Quotient::RoomMessageE
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void MessageEventModel::createReactionModelForEvent(const Quotient::RoomMessageEvent *event)
|
||||||
|
{
|
||||||
|
if (event == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto eventId = event->id();
|
||||||
|
const auto &annotations = m_currentRoom->relatedEvents(eventId, EventRelation::Annotation());
|
||||||
|
if (annotations.isEmpty()) {
|
||||||
|
if (m_reactionModels.contains(eventId)) {
|
||||||
|
delete m_reactionModels[eventId];
|
||||||
|
m_reactionModels.remove(eventId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
QMap<QString, QList<NeoChatUser *>> reactions = {};
|
||||||
|
for (const auto &a : annotations) {
|
||||||
|
if (a->isRedacted()) { // Just in case?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const auto &e = eventCast<const ReactionEvent>(a)) {
|
||||||
|
reactions[e->relation().key].append(static_cast<NeoChatUser *>(m_currentRoom->user(e->senderId())));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reactions.isEmpty()) {
|
||||||
|
if (m_reactionModels.contains(eventId)) {
|
||||||
|
delete m_reactionModels[eventId];
|
||||||
|
m_reactionModels.remove(eventId);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<ReactionModel::Reaction> res;
|
||||||
|
auto i = reactions.constBegin();
|
||||||
|
while (i != reactions.constEnd()) {
|
||||||
|
QVariantList authors;
|
||||||
|
for (const auto &author : i.value()) {
|
||||||
|
authors.append(userInContext(author, m_currentRoom));
|
||||||
|
}
|
||||||
|
|
||||||
|
res.append(ReactionModel::Reaction{i.key(), authors});
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_reactionModels.contains(eventId)) {
|
||||||
|
m_reactionModels[eventId]->setReactions(res);
|
||||||
|
} else if (res.size() > 0) {
|
||||||
|
m_reactionModels[eventId] = new ReactionModel(this, res, static_cast<NeoChatUser *>(m_currentRoom->localUser()));
|
||||||
|
} else {
|
||||||
|
if (m_reactionModels.contains(eventId)) {
|
||||||
|
delete m_reactionModels[eventId];
|
||||||
|
m_reactionModels.remove(eventId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,6 +8,8 @@
|
|||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
class ReactionModel;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class MessageEventModel
|
* @class MessageEventModel
|
||||||
*
|
*
|
||||||
@@ -90,7 +92,8 @@ public:
|
|||||||
ExcessReadMarkersRole, /**< The number of other users at the event after the first 5. */
|
ExcessReadMarkersRole, /**< The number of other users at the event after the first 5. */
|
||||||
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
|
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
|
||||||
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||||
ReactionRole, /**< List of reactions to this event. */
|
ReactionRole, /**< List model for this event. */
|
||||||
|
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
|
||||||
SourceRole, /**< The full message source JSON. */
|
SourceRole, /**< The full message source JSON. */
|
||||||
|
|
||||||
// For debugging
|
// For debugging
|
||||||
@@ -186,6 +189,7 @@ private:
|
|||||||
bool movingEvent = false;
|
bool movingEvent = false;
|
||||||
|
|
||||||
QMap<QString, LinkPreviewer *> m_linkPreviewers;
|
QMap<QString, LinkPreviewer *> m_linkPreviewers;
|
||||||
|
QMap<QString, ReactionModel *> m_reactionModels;
|
||||||
|
|
||||||
[[nodiscard]] int timelineBaseIndex() const;
|
[[nodiscard]] int timelineBaseIndex() const;
|
||||||
[[nodiscard]] QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const;
|
[[nodiscard]] QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const;
|
||||||
@@ -203,6 +207,7 @@ private:
|
|||||||
QVariantMap getMediaInfoForEvent(const Quotient::RoomEvent &event) const;
|
QVariantMap getMediaInfoForEvent(const Quotient::RoomEvent &event) const;
|
||||||
QVariantMap getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false) const;
|
QVariantMap getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false) const;
|
||||||
void createLinkPreviewerForEvent(const Quotient::RoomMessageEvent *event);
|
void createLinkPreviewerForEvent(const Quotient::RoomMessageEvent *event);
|
||||||
|
void createReactionModelForEvent(const Quotient::RoomMessageEvent *event);
|
||||||
|
|
||||||
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
|
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
|
||||||
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
|
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
|
||||||
|
|||||||
110
src/models/reactionmodel.cpp
Normal file
110
src/models/reactionmodel.cpp
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include "reactionmodel.h"
|
||||||
|
|
||||||
|
#include <QDebug>
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
#include "neochatuser.h"
|
||||||
|
|
||||||
|
ReactionModel::ReactionModel(QObject *parent, QList<Reaction> reactions, NeoChatUser *localUser)
|
||||||
|
: QAbstractListModel(parent)
|
||||||
|
, m_localUser(localUser)
|
||||||
|
{
|
||||||
|
setReactions(reactions);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index.row() >= rowCount()) {
|
||||||
|
qDebug() << "ReactionModel, something's wrong: index.row() >= rowCount()";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto &reaction = m_reactions.at(index.row());
|
||||||
|
|
||||||
|
if (role == TextRole) {
|
||||||
|
if (reaction.authors.count() > 1) {
|
||||||
|
return reaction.reaction + QStringLiteral(" %1").arg(reaction.authors.count());
|
||||||
|
} else {
|
||||||
|
return reaction.reaction;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ReactionRole) {
|
||||||
|
return reaction.reaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ToolTipRole) {
|
||||||
|
QString text;
|
||||||
|
|
||||||
|
for (int i = 0; i < reaction.authors.count() && i < 3; i++) {
|
||||||
|
if (i != 0) {
|
||||||
|
if (i < reaction.authors.count() - 1) {
|
||||||
|
text += QStringLiteral(", ");
|
||||||
|
} else {
|
||||||
|
text += i18nc("Separate the usernames of users", " and ");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
text += reaction.authors.at(i).toMap()["displayName"].toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reaction.authors.count() > 3) {
|
||||||
|
text += i18ncp("%1 is the number of other users", " and %1 other", " and %1 others", reaction.authors.count() - 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
text = i18ncp("%2 is the users who reacted and %3 the emoji that was given",
|
||||||
|
"%2 reacted with %3",
|
||||||
|
"%2 reacted with %3",
|
||||||
|
reaction.authors.count(),
|
||||||
|
text,
|
||||||
|
reaction.reaction);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == AuthorsRole) {
|
||||||
|
return reaction.authors;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == HasLocalUser) {
|
||||||
|
for (auto author : reaction.authors) {
|
||||||
|
if (author.toMap()["id"] == m_localUser->id()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReactionModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return m_reactions.count();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactionModel::setReactions(QList<Reaction> reactions)
|
||||||
|
{
|
||||||
|
beginResetModel();
|
||||||
|
m_reactions.clear();
|
||||||
|
m_reactions = reactions;
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{TextRole, "text"},
|
||||||
|
{ReactionRole, "reaction"},
|
||||||
|
{ToolTipRole, "toolTip"},
|
||||||
|
{AuthorsRole, "authors"},
|
||||||
|
{HasLocalUser, "hasLocalUser"},
|
||||||
|
};
|
||||||
|
}
|
||||||
72
src/models/reactionmodel.h
Normal file
72
src/models/reactionmodel.h
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QAbstractListModel>
|
||||||
|
|
||||||
|
class NeoChatUser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ReactionModel
|
||||||
|
*
|
||||||
|
* This class defines the model for visualising a list of reactions to an event.
|
||||||
|
*/
|
||||||
|
class ReactionModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Definition of an reaction.
|
||||||
|
*/
|
||||||
|
struct Reaction {
|
||||||
|
QString reaction; /**< The reaction emoji. */
|
||||||
|
QVariantList authors; /**< The list of authors who sent the given reaction. */
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
|
enum Roles {
|
||||||
|
TextRole = 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. */
|
||||||
|
};
|
||||||
|
|
||||||
|
ReactionModel(QObject *parent = nullptr, QList<Reaction> reactions = {}, NeoChatUser *localUser = nullptr);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Set the reactions data in the model.
|
||||||
|
*/
|
||||||
|
void setReactions(QList<Reaction> reactions);
|
||||||
|
|
||||||
|
private:
|
||||||
|
QList<Reaction> m_reactions;
|
||||||
|
|
||||||
|
NeoChatUser *m_localUser;
|
||||||
|
};
|
||||||
|
Q_DECLARE_METATYPE(ReactionModel *)
|
||||||
@@ -4,67 +4,64 @@
|
|||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick.Layouts 1.15
|
|
||||||
|
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
Flow {
|
Flow {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The reaction model to get the reactions from.
|
||||||
|
*/
|
||||||
|
property alias model: reactionRepeater.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The given reaction has been clicked.
|
||||||
|
*
|
||||||
|
* Thrown when one of the reaction buttons in the flow is clicked.
|
||||||
|
*/
|
||||||
|
signal reactionClicked(string reaction)
|
||||||
|
|
||||||
spacing: Kirigami.Units.smallSpacing
|
spacing: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
Repeater {
|
Repeater {
|
||||||
model: reaction ?? null
|
id: reactionRepeater
|
||||||
|
model: root.model
|
||||||
|
|
||||||
delegate: QQC2.AbstractButton {
|
delegate: QQC2.AbstractButton {
|
||||||
width: Math.max(implicitWidth, height)
|
width: Math.max(reactionTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 4, height)
|
||||||
|
|
||||||
contentItem: QQC2.Label {
|
contentItem: QQC2.Label {
|
||||||
|
id: reactionLabel
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
text: modelData.reaction + " " + modelData.count
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
text: model.text
|
||||||
|
|
||||||
|
TextMetrics {
|
||||||
|
id: reactionTextMetrics
|
||||||
|
text: reactionLabel.text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
padding: Kirigami.Units.smallSpacing
|
padding: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
background: Kirigami.ShadowedRectangle {
|
background: Kirigami.ShadowedRectangle {
|
||||||
color: checked ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
color: model.hasLocalUser ? Kirigami.Theme.positiveBackgroundColor : Kirigami.Theme.backgroundColor
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
shadow.size: Kirigami.Units.smallSpacing
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
shadow.color: !model.isHighlighted ? 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)
|
shadow.color: !model.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)
|
||||||
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
border.width: 1
|
border.width: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
checkable: true
|
onClicked: reactionClicked(model.reaction)
|
||||||
|
|
||||||
checked: modelData.hasLocalUser
|
|
||||||
|
|
||||||
onToggled: currentRoom.toggleReaction(eventId, modelData.reaction)
|
|
||||||
|
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
|
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
QQC2.ToolTip.text: {
|
QQC2.ToolTip.text: model.toolTip
|
||||||
var text = "";
|
|
||||||
|
|
||||||
for (var i = 0; i < modelData.authors.length && i < 3; i++) {
|
|
||||||
if (i !== 0) {
|
|
||||||
if (i < modelData.authors.length - 1) {
|
|
||||||
text += ", "
|
|
||||||
} else {
|
|
||||||
text += i18nc("Separate the usernames of users", " and ")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text += currentRoom.htmlSafeMemberName(modelData.authors[i].id)
|
|
||||||
}
|
|
||||||
if (modelData.authors.length > 3) {
|
|
||||||
text += i18ncp("%1 is the number of other users", " and %1 other", " and %1 others", modelData.authors.length - 3)
|
|
||||||
}
|
|
||||||
|
|
||||||
text = i18ncp("%2 is the users who reacted and %3 the emoji that was given", "%2 reacted with %3", "%2 reacted with %3", modelData.authors.length, text, modelData.reaction)
|
|
||||||
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -334,7 +334,10 @@ ColumnLayout {
|
|||||||
Layout.leftMargin: showUserMessageOnRight ? 0 : bubble.x + bubble.anchors.leftMargin
|
Layout.leftMargin: showUserMessageOnRight ? 0 : bubble.x + bubble.anchors.leftMargin
|
||||||
Layout.rightMargin: showUserMessageOnRight ? Kirigami.Units.largeSpacing : 0
|
Layout.rightMargin: showUserMessageOnRight ? Kirigami.Units.largeSpacing : 0
|
||||||
|
|
||||||
visible: delegateType !== MessageEventModel.State && delegateType !== MessageEventModel.Notice && reaction != undefined && reaction.length > 0
|
visible: showReactions
|
||||||
|
model: reaction
|
||||||
|
|
||||||
|
onReactionClicked: (reaction) => currentRoom.toggleReaction(eventId, reaction)
|
||||||
}
|
}
|
||||||
AvatarFlow {
|
AvatarFlow {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
|
|||||||
Reference in New Issue
Block a user