Loading and End of Timeline Delegates
Add delegate for showing the user a loading indicator and for the beginning of the timeline. BUG: 455045 BUG: 465285
This commit is contained in:
@@ -389,13 +389,7 @@ int MessageEventModel::rowCount(const QModelIndex &parent) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto firstIt = m_currentRoom->messageEvents().crbegin();
|
||||
if (firstIt != m_currentRoom->messageEvents().crend()) {
|
||||
const auto &firstEvt = **firstIt;
|
||||
return m_currentRoom->timelineSize() + (lastReadEventId != firstEvt.id() ? 1 : 0);
|
||||
} else {
|
||||
return m_currentRoom->timelineSize();
|
||||
}
|
||||
return int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize() + (m_lastReadEventIndex.isValid() ? 1 : 0);
|
||||
}
|
||||
|
||||
bool MessageEventModel::canFetchMore(const QModelIndex &parent) const
|
||||
@@ -422,7 +416,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
const auto row = idx.row();
|
||||
|
||||
if (!m_currentRoom || row < 0 || row >= int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize()) {
|
||||
if (!m_currentRoom || row < 0 || row >= rowCount()) {
|
||||
return {};
|
||||
};
|
||||
|
||||
@@ -465,7 +459,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
|
||||
: i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason());
|
||||
}
|
||||
|
||||
return eventHandler.getRichBody();
|
||||
}
|
||||
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
#include "enums/delegatetype.h"
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "timelinemodel.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
MessageFilterModel::MessageFilterModel(QObject *parent, MessageEventModel *sourceMessageModel)
|
||||
MessageFilterModel::MessageFilterModel(QObject *parent, TimelineModel *sourceModel)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
Q_ASSERT(sourceMessageModel);
|
||||
setSourceModel(sourceMessageModel);
|
||||
Q_ASSERT(sourceModel);
|
||||
setSourceModel(sourceModel);
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowStateEventChanged, this, [this] {
|
||||
invalidateFilter();
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "timelinemodel.h"
|
||||
|
||||
/**
|
||||
* @class MessageFilterModel
|
||||
@@ -36,7 +37,7 @@ public:
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
|
||||
explicit MessageFilterModel(QObject *parent = nullptr, MessageEventModel *sourceMessageModel = nullptr);
|
||||
explicit MessageFilterModel(QObject *parent = nullptr, TimelineModel *sourceModel = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Custom filter function to remove hidden messages.
|
||||
|
||||
95
src/models/timelinemodel.cpp
Normal file
95
src/models/timelinemodel.cpp
Normal file
@@ -0,0 +1,95 @@
|
||||
// SPDX-FileCopyrightText: 2022 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 "timelinemodel.h"
|
||||
|
||||
#include "delegatetype.h"
|
||||
|
||||
TimelineModel::TimelineModel(QObject *parent)
|
||||
: QConcatenateTablesProxyModel(parent)
|
||||
{
|
||||
m_messageEventModel = new MessageEventModel(this);
|
||||
addSourceModel(m_messageEventModel);
|
||||
m_timelineEndModel = new TimelineEndModel(this);
|
||||
addSourceModel(m_timelineEndModel);
|
||||
}
|
||||
|
||||
NeoChatRoom *TimelineModel::room() const
|
||||
{
|
||||
return m_messageEventModel->room();
|
||||
}
|
||||
|
||||
void TimelineModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
// Both models do their own null checking so just pass along.
|
||||
m_messageEventModel->setRoom(room);
|
||||
m_timelineEndModel->setRoom(room);
|
||||
}
|
||||
|
||||
MessageEventModel *TimelineModel::messageEventModel() const
|
||||
{
|
||||
return m_messageEventModel;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> TimelineModel::roleNames() const
|
||||
{
|
||||
return m_messageEventModel->roleNames();
|
||||
}
|
||||
|
||||
TimelineEndModel::TimelineEndModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void TimelineEndModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (room == m_room) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
|
||||
if (m_room != nullptr) {
|
||||
m_room->disconnect(this);
|
||||
}
|
||||
|
||||
m_room = room;
|
||||
|
||||
if (m_room != nullptr) {
|
||||
connect(m_room, &Quotient::Room::eventsHistoryJobChanged, this, [this]() {
|
||||
if (m_room->allHistoryLoaded()) {
|
||||
// HACK: We have to do it this way because DelegateChooser doesn't update dynamically.
|
||||
beginRemoveRows({}, 0, 0);
|
||||
endRemoveRows();
|
||||
beginInsertRows({}, 0, 0);
|
||||
endInsertRows();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant TimelineEndModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
Q_UNUSED(idx)
|
||||
if (m_room == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
return m_room->allHistoryLoaded() ? DelegateType::TimelineEnd : DelegateType::Loading;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int TimelineEndModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> TimelineEndModel::roleNames() const
|
||||
{
|
||||
return {{DelegateTypeRole, "delegateType"}};
|
||||
}
|
||||
112
src/models/timelinemodel.h
Normal file
112
src/models/timelinemodel.h
Normal file
@@ -0,0 +1,112 @@
|
||||
// SPDX-FileCopyrightText: 2022 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>
|
||||
#include <QConcatenateTablesProxyModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
/**
|
||||
* @class TimelineEndModel
|
||||
*
|
||||
* A model to provide a single delegate to mark the end of the timeline.
|
||||
*
|
||||
* The delegate will either be a loading delegate if more events are being loaded
|
||||
* or a timeline end delegate if all history is loaded.
|
||||
*/
|
||||
class TimelineEndModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
DelegateTypeRole = MessageEventModel::DelegateTypeRole, /**< The delegate type of the message. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit TimelineEndModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Set the room for the timeline.
|
||||
*/
|
||||
void setRoom(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, the answer is always 1.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a map with DelegateTypeRole it's the only one.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class TimelineModel
|
||||
*
|
||||
* A model to visualise a room timeline.
|
||||
*
|
||||
* This model combines a MessageEventModel with a TimelineEndModel.
|
||||
*
|
||||
* @sa MessageEventModel, TimelineEndModel
|
||||
*/
|
||||
class TimelineModel : public QConcatenateTablesProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is getting its messages from.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
/**
|
||||
* @brief The MessageEventModel for the timeline.
|
||||
*/
|
||||
Q_PROPERTY(MessageEventModel *messageEventModel READ messageEventModel CONSTANT)
|
||||
|
||||
public:
|
||||
TimelineModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
MessageEventModel *messageEventModel() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractProxyModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
|
||||
private:
|
||||
MessageEventModel *m_messageEventModel = nullptr;
|
||||
TimelineEndModel *m_timelineEndModel = nullptr;
|
||||
};
|
||||
Reference in New Issue
Block a user