Use the ChatBar Component for new thread messages

![image](/uploads/a24dea919e7f165d602991659f517c22/image.png){width=148 height=210}

Note: there is still an issue where after starting a new thread the threaded messages only appear after a restart as the root event needs re-downloading from the server to get the thread info added. My plan is to tackle this next.
This commit is contained in:
James Graham
2024-08-26 19:13:19 +00:00
parent c0151353c5
commit 1d7ed1983b
11 changed files with 166 additions and 30 deletions

View File

@@ -140,6 +140,13 @@ void MessageContentModel::initializeModel()
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();
});
@@ -184,12 +191,7 @@ void MessageContentModel::intiializeEvent(const Quotient::RoomEvent *event)
{
m_event = loadEvent<RoomEvent>(event->fullJson());
// a pending event may not previously have had an event ID so update.
if (m_eventId.isEmpty()) {
m_eventId = m_event->id();
if (m_eventId.isEmpty()) {
m_eventId = m_event->transactionId();
}
}
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
@@ -341,6 +343,12 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
}
}
if (role == ChatBarCacheRole) {
if (m_room->threadCache()->threadId() == m_eventId) {
return QVariant::fromValue<ChatBarCache *>(m_room->threadCache());
}
return QVariant::fromValue<ChatBarCache *>(m_room->editCache());
}
return {};
}
@@ -372,6 +380,7 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
roles[ReplyAuthorRole] = "replyAuthor";
roles[ReplyContentModelRole] = "replyContentModel";
roles[LinkPreviewerRole] = "linkPreviewer";
roles[ChatBarCacheRole] = "chatBarCache";
return roles;
}
@@ -400,7 +409,7 @@ void MessageContentModel::resetModel()
endResetModel();
}
void MessageContentModel::resetContent(bool isEditing)
void MessageContentModel::resetContent(bool isEditing, bool isThreading)
{
Q_ASSERT(m_event != nullptr);
@@ -409,7 +418,7 @@ void MessageContentModel::resetContent(bool isEditing)
m_components.remove(startRow, rowCount() - startRow);
endRemoveRows();
const auto newComponents = messageContentComponents(isEditing);
const auto newComponents = messageContentComponents(isEditing, isThreading);
if (newComponents.size() == 0) {
return;
}
@@ -418,7 +427,7 @@ void MessageContentModel::resetContent(bool isEditing)
endInsertRows();
}
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing)
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
{
QList<MessageComponent> newComponents;
@@ -438,7 +447,7 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
}
if (isEditing) {
newComponents += MessageComponent{MessageComponentType::Edit, QString(), {}};
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
} else {
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*m_event.get())));
}
@@ -447,6 +456,11 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
newComponents = addLinkPreviews(newComponents);
}
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
if (isThreading && !EventHandler::isThreaded(m_event.get())) {
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
}
return newComponents;
}

View File

@@ -70,6 +70,7 @@ public:
ReplyContentModelRole, /**< The MessageContentModel for the reply event. */
LinkPreviewerRole, /**< The link preview details. */
ChatBarCacheRole, /**< The ChatBarCache to use. */
};
Q_ENUM(Roles)
@@ -133,8 +134,8 @@ private:
QList<MessageComponent> m_components;
void resetModel();
void resetContent(bool isEditing = false);
QList<MessageComponent> messageContentComponents(bool isEditing = false);
void resetContent(bool isEditing = false, bool isThreading = false);
QList<MessageComponent> messageContentComponents(bool isEditing = false, bool isThreading = false);
QPointer<MessageContentModel> m_replyModel;
void updateReplyModel();

View File

@@ -9,12 +9,15 @@
#include <Quotient/omittable.h>
#include <memory>
#include "chatbarcache.h"
#include "eventhandler.h"
#include "messagecomponenttype.h"
#include "neochatroom.h"
ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
: QConcatenateTablesProxyModel(room)
, m_threadRootId(threadRootId)
, m_threadChatBarModel(new ThreadChatBarModel(this, room))
{
Q_ASSERT(!m_threadRootId.isEmpty());
Q_ASSERT(room);
@@ -25,7 +28,6 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
addNewEvent(event);
clearModels();
addModels();
}
}
@@ -38,7 +40,6 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
}
}
}
clearModels();
addModels();
});
@@ -46,6 +47,11 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
addModels();
}
QString ThreadModel::threadRootId() const
{
return m_threadRootId;
}
MessageContentModel *ThreadModel::threadRootContentModel() const
{
return m_threadRootContentModel.get();
@@ -77,7 +83,6 @@ void ThreadModel::fetchMore(const QModelIndex &parent)
m_contentModels.push_back(new MessageContentModel(room, event.get()));
}
clearModels();
addModels();
const auto newNextBatch = m_currentJob->nextBatch();
@@ -103,13 +108,15 @@ void ThreadModel::addNewEvent(const Quotient::RoomEvent *event)
void ThreadModel::addModels()
{
if (!sourceModels().isEmpty()) {
clearModels();
}
addSourceModel(m_threadRootContentModel.get());
for (auto it = m_contentModels.crbegin(); it != m_contentModels.crend(); ++it) {
addSourceModel(*it);
}
beginResetModel();
endResetModel();
addSourceModel(m_threadChatBarModel);
}
void ThreadModel::clearModels()
@@ -120,6 +127,61 @@ void ThreadModel::clearModels()
removeSourceModel(model);
}
}
removeSourceModel(m_threadChatBarModel);
}
ThreadChatBarModel::ThreadChatBarModel(QObject *parent, NeoChatRoom *room)
: QAbstractListModel(parent)
, m_room(room)
{
if (m_room != nullptr) {
connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) {
const auto threadModel = dynamic_cast<ThreadModel *>(this->parent());
if (threadModel != nullptr && (oldThreadId == threadModel->threadRootId() || newThreadId == threadModel->threadRootId())) {
beginResetModel();
endResetModel();
}
});
}
}
QVariant ThreadChatBarModel::data(const QModelIndex &idx, int role) const
{
if (idx.row() > 1) {
return {};
}
if (role == ComponentTypeRole) {
return MessageComponentType::ChatBar;
}
if (role == ChatBarCacheRole) {
if (m_room == nullptr) {
return {};
}
return QVariant::fromValue<ChatBarCache *>(m_room->threadCache());
}
return {};
}
int ThreadChatBarModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
if (m_room == nullptr) {
return 0;
}
const auto threadModel = dynamic_cast<ThreadModel *>(this->parent());
if (threadModel != nullptr) {
return m_room->threadCache()->threadId() == threadModel->threadRootId() ? 1 : 0;
}
return 0;
}
QHash<int, QByteArray> ThreadChatBarModel::roleNames() const
{
return {
{ComponentTypeRole, "componentType"},
{ChatBarCacheRole, "chatBarCache"},
};
}
#include "moc_threadmodel.cpp"

View File

@@ -21,6 +21,57 @@
class NeoChatRoom;
class ReactionModel;
/**
* @class ThreadChatBarModel
*
* A model to provide a chat bar component to send new messages in a thread.
*/
class ThreadChatBarModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
public:
/**
* @brief Defines the model roles.
*
* The role values need to match MessageContentModel not to blow up.
*
* @sa MessageContentModel
*/
enum Roles {
ComponentTypeRole = MessageContentModel::ComponentTypeRole, /**< The type of component to visualise the message. */
ChatBarCacheRole = MessageContentModel::ChatBarCacheRole, /**< The ChatBarCache to use. */
};
Q_ENUM(Roles)
explicit ThreadChatBarModel(QObject *parent, NeoChatRoom *room);
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
/**
* @brief 1 or 0, depending on whether a chat bar should be shown.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a map with ComponentTypeRole it's the only one.
*
* @sa Roles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
private:
QPointer<NeoChatRoom> m_room;
};
/**
* @class ThreadModel
*
@@ -38,6 +89,8 @@ class ThreadModel : public QConcatenateTablesProxyModel
public:
explicit ThreadModel(const QString &threadRootId, NeoChatRoom *room);
QString threadRootId() const;
/**
* @brief The content model for the thread root event.
*/
@@ -70,6 +123,7 @@ private:
std::unique_ptr<MessageContentModel> m_threadRootContentModel;
std::deque<MessageContentModel *> m_contentModels;
ThreadChatBarModel *m_threadChatBarModel;
QList<QString> m_events;
QList<QString> m_pendingEvents;