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:
James Graham
2023-11-20 17:10:56 +00:00
parent 0dbef58ff2
commit 5efd17d370
15 changed files with 370 additions and 41 deletions

View File

@@ -141,6 +141,8 @@ add_library(neochat STATIC
colorschemer.h colorschemer.h
models/notificationsmodel.cpp models/notificationsmodel.cpp
models/notificationsmodel.h models/notificationsmodel.h
models/timelinemodel.cpp
models/timelinemodel.h
) )
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
@@ -293,6 +295,8 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/SelectSpacesDialog.qml qml/SelectSpacesDialog.qml
qml/AttachDialog.qml qml/AttachDialog.qml
qml/NotificationsView.qml qml/NotificationsView.qml
qml/LoadingDelegate.qml
qml/TimelineEndDelegate.qml
RESOURCES RESOURCES
qml/confetti.png qml/confetti.png
qml/glowdot.png qml/glowdot.png

View File

@@ -40,6 +40,8 @@ public:
Poll, /**< The initial event for a poll. */ Poll, /**< The initial event for a poll. */
Location, /**< A location event. */ Location, /**< A location event. */
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */ LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
Loading, /**< A delegate to tell the user more messages are being loaded. */
TimelineEnd, /**< A delegate to inform that all messages are loaded. */
Other, /**< Anything that cannot be classified as another type. */ Other, /**< Anything that cannot be classified as another type. */
}; };
Q_ENUM(Type); Q_ENUM(Type);

View File

@@ -389,13 +389,7 @@ int MessageEventModel::rowCount(const QModelIndex &parent) const
return 0; return 0;
} }
const auto firstIt = m_currentRoom->messageEvents().crbegin(); return int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize() + (m_lastReadEventIndex.isValid() ? 1 : 0);
if (firstIt != m_currentRoom->messageEvents().crend()) {
const auto &firstEvt = **firstIt;
return m_currentRoom->timelineSize() + (lastReadEventId != firstEvt.id() ? 1 : 0);
} else {
return m_currentRoom->timelineSize();
}
} }
bool MessageEventModel::canFetchMore(const QModelIndex &parent) const 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(); 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 {}; return {};
}; };
@@ -465,7 +459,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>") return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
: i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason()); : i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason());
} }
return eventHandler.getRichBody(); return eventHandler.getRichBody();
} }

View File

@@ -8,14 +8,15 @@
#include "enums/delegatetype.h" #include "enums/delegatetype.h"
#include "messageeventmodel.h" #include "messageeventmodel.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "timelinemodel.h"
using namespace Quotient; using namespace Quotient;
MessageFilterModel::MessageFilterModel(QObject *parent, MessageEventModel *sourceMessageModel) MessageFilterModel::MessageFilterModel(QObject *parent, TimelineModel *sourceModel)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {
Q_ASSERT(sourceMessageModel); Q_ASSERT(sourceModel);
setSourceModel(sourceMessageModel); setSourceModel(sourceModel);
connect(NeoChatConfig::self(), &NeoChatConfig::ShowStateEventChanged, this, [this] { connect(NeoChatConfig::self(), &NeoChatConfig::ShowStateEventChanged, this, [this] {
invalidateFilter(); invalidateFilter();

View File

@@ -7,6 +7,7 @@
#include <QSortFilterProxyModel> #include <QSortFilterProxyModel>
#include "messageeventmodel.h" #include "messageeventmodel.h"
#include "timelinemodel.h"
/** /**
* @class MessageFilterModel * @class MessageFilterModel
@@ -36,7 +37,7 @@ public:
LastRole, // Keep this last 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. * @brief Custom filter function to remove hidden messages.

View 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
View 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;
};

View File

@@ -99,6 +99,7 @@ DelegateChooser {
connection: root.connection connection: root.connection
} }
} }
DelegateChoice { DelegateChoice {
roleValue: DelegateType.LiveLocation roleValue: DelegateType.LiveLocation
delegate: LiveLocationDelegate { delegate: LiveLocationDelegate {
@@ -107,6 +108,18 @@ DelegateChooser {
} }
} }
DelegateChoice {
roleValue: DelegateType.Loading
delegate: LoadingDelegate {}
}
DelegateChoice {
roleValue: DelegateType.TimelineEnd
delegate: TimelineEndDelegate {
room: root.room
}
}
DelegateChoice { DelegateChoice {
roleValue: DelegateType.Other roleValue: DelegateType.Other
delegate: Item {} delegate: Item {}

View File

@@ -0,0 +1,15 @@
// 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
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.neochat
TimelineDelegate {
id: root
contentItem: Kirigami.PlaceholderMessage {
text: i18n("Loading…")
}
}

View File

@@ -23,17 +23,17 @@ Kirigami.Page {
required property NeoChatConnection connection required property NeoChatConnection connection
/** /**
* @brief The MessageEventModel to use. * @brief The TimelineModel to use.
* *
* Required so that new events can be requested when the end of the current * Required so that new events can be requested when the end of the current
* local timeline is reached. * local timeline is reached.
* *
* @note For loading a room in a different window, override this with a new * @note For loading a room in a different window, override this with a new
* MessageEventModel set with the room to be shown. * TimelineModel set with the room to be shown.
* *
* @sa MessageEventModel * @sa TimelineModel
*/ */
property MessageEventModel messageEventModel: RoomManager.messageEventModel property TimelineModel timelineModel: RoomManager.timelineModel
/** /**
* @brief The MessageFilterModel to use. * @brief The MessageFilterModel to use.
@@ -41,9 +41,9 @@ Kirigami.Page {
* This model has the filtered list of events that should be shown in the timeline. * This model has the filtered list of events that should be shown in the timeline.
* *
* @note For loading a room in a different window, override this with a new * @note For loading a room in a different window, override this with a new
* MessageFilterModel with the new MessageEventModel as the source model. * MessageFilterModel with the new TimelineModel as the source model.
* *
* @sa MessageEventModel, MessageFilterModel * @sa TimelineModel, MessageFilterModel
*/ */
property MessageFilterModel messageFilterModel: RoomManager.messageFilterModel property MessageFilterModel messageFilterModel: RoomManager.messageFilterModel
@@ -56,7 +56,7 @@ Kirigami.Page {
* @note For loading a room in a different window, override this with a new * @note For loading a room in a different window, override this with a new
* MediaMessageFilterModel with the new MessageFilterModel as the source model. * MediaMessageFilterModel with the new MessageFilterModel as the source model.
* *
* @sa MessageEventModel, MessageFilterModel * @sa TimelineModel, MessageFilterModel
*/ */
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
@@ -120,7 +120,7 @@ Kirigami.Page {
sourceComponent: TimelineView { sourceComponent: TimelineView {
id: timelineView id: timelineView
currentRoom: root.currentRoom currentRoom: root.currentRoom
messageEventModel: root.messageEventModel timelineModel: root.timelineModel
messageFilterModel: root.messageFilterModel messageFilterModel: root.messageFilterModel
actionsHandler: root.actionsHandler actionsHandler: root.actionsHandler
onFocusChatBar: { onFocusChatBar: {

View File

@@ -29,7 +29,7 @@ Kirigami.ApplicationWindow {
disableCancelShortcut: true disableCancelShortcut: true
connection: root.connection connection: root.connection
messageEventModel: MessageEventModel { timelineModel: TimelineModel {
room: currentRoom room: currentRoom
} }
messageFilterModel: MessageFilterModel { messageFilterModel: MessageFilterModel {

View File

@@ -0,0 +1,90 @@
// 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
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
TimelineDelegate {
id: root
/**
* @brief The current room that user is viewing.
*/
required property NeoChatRoom room
contentItem: ColumnLayout {
RowLayout {
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.large
Layout.preferredHeight: Kirigami.Units.iconSizes.large
name: root.room ? root.room.displayName : ""
source: root.room && root.room.avatarMediaId ? ("image://mxc/" + root.room.avatarMediaId) : ""
Rectangle {
visible: room.usesEncryption
color: Kirigami.Theme.backgroundColor
width: Kirigami.Units.gridUnit
height: Kirigami.Units.gridUnit
anchors {
bottom: parent.bottom
right: parent.right
}
radius: Math.round(width / 2)
Kirigami.Icon {
source: "channel-secure-symbolic"
anchors.fill: parent
}
}
}
ColumnLayout {
Layout.fillWidth: true
Layout.alignment: Qt.AlignVCenter
spacing: 0
Kirigami.Heading {
Layout.fillWidth: true
text: root.room ? root.room.displayName : i18n("No name")
textFormat: Text.PlainText
wrapMode: Text.Wrap
}
Kirigami.SelectableLabel {
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
textFormat: TextEdit.PlainText
visible: root.room && root.room.canonicalAlias
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
}
}
}
Kirigami.SelectableLabel {
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
text: i18n("This is the beginning of the chat. There are no historical messages beyond this point.")
wrapMode: Text.Wrap
onLinkActivated: link => UrlHelper.openUrl(link)
}
}
}

View File

@@ -28,12 +28,12 @@ QQC2.ScrollView {
property bool roomChanging: false property bool roomChanging: false
/** /**
* @brief The MessageEventModel to use. * @brief The TimelineModel to use.
* *
* Required so that new events can be requested when the end of the current * Required so that new events can be requested when the end of the current
* local timeline is reached. * local timeline is reached.
*/ */
required property MessageEventModel messageEventModel required property TimelineModel timelineModel
/** /**
* @brief The MessageFilterModel to use. * @brief The MessageFilterModel to use.
@@ -85,16 +85,16 @@ QQC2.ScrollView {
running: messageListView.atYBeginning running: messageListView.atYBeginning
triggeredOnStart: true triggeredOnStart: true
onTriggered: { onTriggered: {
if (messageListView.atYBeginning && root.messageEventModel.canFetchMore(root.messageEventModel.index(0, 0))) { if (messageListView.atYBeginning && root.timelineModel.messageEventModel.canFetchMore(root.timelineModel.index(0, 0))) {
root.messageEventModel.fetchMore(root.messageEventModel.index(0, 0)); root.timelineModel.messageEventModel.fetchMore(root.timelineModel.index(0, 0));
} }
} }
repeat: true repeat: true
} }
// HACK: The view should do this automatically but doesn't. // HACK: The view should do this automatically but doesn't.
onAtYBeginningChanged: if (atYBeginning && root.messageEventModel.canFetchMore(root.messageEventModel.index(0, 0))) { onAtYBeginningChanged: if (atYBeginning && root.timelineModel.messageEventModel.canFetchMore(root.timelineModel.index(0, 0))) {
root.messageEventModel.fetchMore(root.messageEventModel.index(0, 0)); root.timelineModel.messageEventModel.fetchMore(root.timelineModel.index(0, 0));
} }
Timer { Timer {
@@ -270,7 +270,7 @@ QQC2.ScrollView {
} }
Connections { Connections {
target: root.messageEventModel target: root.timelineModel
function onRowsInserted() { function onRowsInserted() {
markReadIfVisibleTimer.restart() markReadIfVisibleTimer.restart()
@@ -311,7 +311,7 @@ QQC2.ScrollView {
Connections { Connections {
//enabled: Config.showFancyEffects //enabled: Config.showFancyEffects
target: root.messageEventModel target: root.timelineModel.messageEventModel
function onFancyEffectsReasonFound(fancyEffect) { function onFancyEffectsReasonFound(fancyEffect) {
fancyEffectsContainer.processFancyEffectsReason(fancyEffect) fancyEffectsContainer.processFancyEffectsReason(fancyEffect)
@@ -336,10 +336,10 @@ QQC2.ScrollView {
} }
function eventToIndex(eventID) { function eventToIndex(eventID) {
const index = root.messageEventModel.eventIdToRow(eventID) const index = root.timelineModel.messageEventModel.eventIdToRow(eventID)
if (index === -1) if (index === -1)
return -1 return -1
return root.messageFilterModel.mapFromSource(root.messageEventModel.index(index, 0)).row return root.messageFilterModel.mapFromSource(root.timelineModel.index(index, 0)).row
} }
function firstVisibleIndex() { function firstVisibleIndex() {

View File

@@ -8,8 +8,10 @@
#include "controller.h" #include "controller.h"
#include "enums/delegatetype.h" #include "enums/delegatetype.h"
#include "models/messageeventmodel.h" #include "models/messageeventmodel.h"
#include "models/timelinemodel.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatroom.h" #include "neochatroom.h"
#include <KLocalizedString> #include <KLocalizedString>
#include <QDesktopServices> #include <QDesktopServices>
#include <QQuickTextDocument> #include <QQuickTextDocument>
@@ -29,14 +31,14 @@ RoomManager::RoomManager(QObject *parent)
, m_currentRoom(nullptr) , m_currentRoom(nullptr)
, m_lastCurrentRoom(nullptr) , m_lastCurrentRoom(nullptr)
, m_config(KSharedConfig::openStateConfig()) , m_config(KSharedConfig::openStateConfig())
, m_messageEventModel(new MessageEventModel(this)) , m_timelineModel(new TimelineModel(this))
, m_messageFilterModel(new MessageFilterModel(this, m_messageEventModel)) , m_messageFilterModel(new MessageFilterModel(this, m_timelineModel))
, m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel)) , m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel))
{ {
m_lastRoomConfig = m_config->group(QStringLiteral("LastOpenRoom")); m_lastRoomConfig = m_config->group(QStringLiteral("LastOpenRoom"));
connect(this, &RoomManager::currentRoomChanged, this, [this]() { connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_messageEventModel->setRoom(m_currentRoom); m_timelineModel->setRoom(m_currentRoom);
}); });
} }
@@ -55,9 +57,9 @@ NeoChatRoom *RoomManager::currentRoom() const
return m_currentRoom; return m_currentRoom;
} }
MessageEventModel *RoomManager::messageEventModel() const TimelineModel *RoomManager::timelineModel() const
{ {
return m_messageEventModel; return m_timelineModel;
} }
MessageFilterModel *RoomManager::messageFilterModel() const MessageFilterModel *RoomManager::messageFilterModel() const

View File

@@ -15,6 +15,7 @@
#include "models/mediamessagefiltermodel.h" #include "models/mediamessagefiltermodel.h"
#include "models/messageeventmodel.h" #include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h" #include "models/messagefiltermodel.h"
#include "models/timelinemodel.h"
class NeoChatRoom; class NeoChatRoom;
class NeoChatConnection; class NeoChatConnection;
@@ -48,7 +49,7 @@ class RoomManager : public QObject, public UriResolverBase
Q_PROPERTY(NeoChatRoom *currentRoom READ currentRoom NOTIFY currentRoomChanged) Q_PROPERTY(NeoChatRoom *currentRoom READ currentRoom NOTIFY currentRoomChanged)
/** /**
* @brief The MessageEventModel that should be used for room message visualisation. * @brief The TimelineModel that should be used for room message visualisation.
* *
* The room object the model uses to get the data will be updated by this class * The room object the model uses to get the data will be updated by this class
* so there is no need to do this manually or replace the model when a room * so there is no need to do this manually or replace the model when a room
@@ -57,7 +58,7 @@ class RoomManager : public QObject, public UriResolverBase
* @note Available here so that the room page and drawer both have access to the * @note Available here so that the room page and drawer both have access to the
* same model. * same model.
*/ */
Q_PROPERTY(MessageEventModel *messageEventModel READ messageEventModel CONSTANT) Q_PROPERTY(TimelineModel *timelineModel READ timelineModel CONSTANT)
/** /**
* @brief The MessageFilterModel that should be used for room message visualisation. * @brief The MessageFilterModel that should be used for room message visualisation.
@@ -101,7 +102,7 @@ public:
NeoChatRoom *currentRoom() const; NeoChatRoom *currentRoom() const;
MessageEventModel *messageEventModel() const; TimelineModel *timelineModel() const;
MessageFilterModel *messageFilterModel() const; MessageFilterModel *messageFilterModel() const;
MediaMessageFilterModel *mediaMessageFilterModel() const; MediaMessageFilterModel *mediaMessageFilterModel() const;
@@ -383,7 +384,7 @@ private:
KConfigGroup m_lastRoomConfig; KConfigGroup m_lastRoomConfig;
QPointer<ChatDocumentHandler> m_chatDocumentHandler; QPointer<ChatDocumentHandler> m_chatDocumentHandler;
MessageEventModel *m_messageEventModel; TimelineModel *m_timelineModel;
MessageFilterModel *m_messageFilterModel; MessageFilterModel *m_messageFilterModel;
MediaMessageFilterModel *m_mediaMessageFilterModel; MediaMessageFilterModel *m_mediaMessageFilterModel;
NeoChatConnection *m_connection; NeoChatConnection *m_connection;