Use the ChatBar Component for new thread messages
{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:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user