From 442612d31dc59527b60a152f55b1d28bdbf51edf Mon Sep 17 00:00:00 2001 From: James Graham Date: Mon, 28 Aug 2023 10:31:18 +0000 Subject: [PATCH] Merge the functionality of CollapseStateProxyModel into MessageFilterModel Merge the functionality of CollapseStateProxyModel into MessageFilterModel there is no need for a whole separate model the filters can be combined trivialy. --- src/CMakeLists.txt | 2 - src/main.cpp | 2 - src/models/collapsestateproxymodel.cpp | 177 ------------------------- src/models/collapsestateproxymodel.h | 80 ----------- src/models/mediamessagefiltermodel.h | 4 +- src/models/messagefiltermodel.cpp | 174 +++++++++++++++++++++++- src/models/messagefiltermodel.h | 58 ++++++++ src/qml/Component/TimelineView.qml | 13 +- 8 files changed, 238 insertions(+), 272 deletions(-) delete mode 100644 src/models/collapsestateproxymodel.cpp delete mode 100644 src/models/collapsestateproxymodel.h diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b707cab61..1012856a8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -74,8 +74,6 @@ add_library(neochat STATIC blurhash.h blurhashimageprovider.cpp blurhashimageprovider.h - models/collapsestateproxymodel.cpp - models/collapsestateproxymodel.h models/mediamessagefiltermodel.cpp models/mediamessagefiltermodel.h urlhelper.cpp diff --git a/src/main.cpp b/src/main.cpp index 78eaca964..49785332d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -54,7 +54,6 @@ #include "login.h" #include "matriximageprovider.h" #include "models/accountemoticonmodel.h" -#include "models/collapsestateproxymodel.h" #include "models/customemojimodel.h" #include "models/devicesmodel.h" #include "models/devicesproxymodel.h" @@ -250,7 +249,6 @@ int main(int argc, char *argv[]) qmlRegisterType("org.kde.neochat", 1, 0, "UserListModel"); qmlRegisterType("org.kde.neochat", 1, 0, "MessageEventModel"); qmlRegisterType("org.kde.neochat", 1, 0, "ReactionModel"); - qmlRegisterType("org.kde.neochat", 1, 0, "CollapseStateProxyModel"); qmlRegisterType("org.kde.neochat", 1, 0, "MediaMessageFilterModel"); qmlRegisterType("org.kde.neochat", 1, 0, "MessageFilterModel"); qmlRegisterType("org.kde.neochat", 1, 0, "UserFilterModel"); diff --git a/src/models/collapsestateproxymodel.cpp b/src/models/collapsestateproxymodel.cpp deleted file mode 100644 index 97ab82f93..000000000 --- a/src/models/collapsestateproxymodel.cpp +++ /dev/null @@ -1,177 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Tobias Fella -// SPDX-License-Identifier: LGPL-2.0-or-later - -#include "collapsestateproxymodel.h" - -#include - -bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const -{ - Q_UNUSED(source_parent); - return sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::DelegateTypeRole) - != MessageEventModel::DelegateType::State // If this is not a state, show it - || (source_row < sourceModel()->rowCount() - 1 - && sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::DelegateTypeRole) - != MessageEventModel::DelegateType::State) // If this is the first state in a block, show it. TODO hidden events? - || sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::ShowSectionRole).toBool(); // If it's a new day, show it -} - -QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const -{ - if (role == AggregateDisplayRole) { - return aggregateEventToString(mapToSource(index).row()); - } else if (role == StateEventsRole) { - return stateEventsList(mapToSource(index).row()); - } else if (role == AuthorListRole) { - return authorList(mapToSource(index).row()); - } else if (role == ExcessAuthorsRole) { - return excessAuthors(mapToSource(index).row()); - } - return sourceModel()->data(mapToSource(index), role); -} - -QHash CollapseStateProxyModel::roleNames() const -{ - auto roles = sourceModel()->roleNames(); - roles[AggregateDisplayRole] = "aggregateDisplay"; - roles[StateEventsRole] = "stateEvents"; - roles[AuthorListRole] = "authorList"; - roles[ExcessAuthorsRole] = "excessAuthors"; - return roles; -} - -QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const -{ - QStringList parts; - QVariantList uniqueAuthors; - for (int i = sourceRow; i >= 0; i--) { - parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString(); - QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); - if (!uniqueAuthors.contains(nextAuthor)) { - uniqueAuthors.append(nextAuthor); - } - if (i > 0 - && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) - != MessageEventModel::DelegateType::State // If it's not a state event - || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible - )) { - break; - } - } - parts.sort(); // Sort them so that all identical events can be collected. - if (!parts.isEmpty()) { - QStringList chunks; - while (!parts.isEmpty()) { - chunks += QString(); - int count = 1; - auto part = parts.takeFirst(); - chunks.last() += part; - while (!parts.isEmpty() && parts.first() == part) { - parts.removeFirst(); - count++; - } - if (count > 1 && uniqueAuthors.length() == 1) { - chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count); - } - } - chunks.removeDuplicates(); - QString text = QStringLiteral(""); // There can be links in the event text so make sure all are styled. - // The author text is either "n users" if > 1 user or the matrix.to link to a single user. - QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length()) - : QStringLiteral("%3 ") - .arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(), - uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(), - uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped()); - text += userText; - text += chunks.takeFirst(); - - if (chunks.size() > 0) { - while (chunks.size() > 1) { - text += i18nc("[action 1], [action 2 and/or action 3]", ", "); - text += chunks.takeFirst(); - } - text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and "); - text += chunks.takeFirst(); - } - return text; - } else { - return {}; - } -} - -QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const -{ - QVariantList stateEvents; - for (int i = sourceRow; i >= 0; i--) { - auto nextState = QVariantMap{ - {QStringLiteral("author"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)}, - {QStringLiteral("authorDisplayName"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()}, - {QStringLiteral("text"), sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()}, - }; - stateEvents.append(nextState); - if (i > 0 - && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) - != MessageEventModel::DelegateType::State // If it's not a state event - || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible - )) { - break; - } - } - return stateEvents; -} - -QVariantList CollapseStateProxyModel::authorList(int sourceRow) const -{ - QVariantList uniqueAuthors; - for (int i = sourceRow; i >= 0; i--) { - QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); - if (!uniqueAuthors.contains(nextAvatar)) { - uniqueAuthors.append(nextAvatar); - } - if (i > 0 - && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) - != MessageEventModel::DelegateType::State // If it's not a state event - || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible - )) { - break; - } - } - - if (uniqueAuthors.count() > 5) { - uniqueAuthors = uniqueAuthors.mid(0, 5); - } - return uniqueAuthors; -} - -QString CollapseStateProxyModel::excessAuthors(int row) const -{ - QVariantList uniqueAuthors; - for (int i = row; i >= 0; i--) { - QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); - if (!uniqueAuthors.contains(nextAvatar)) { - uniqueAuthors.append(nextAvatar); - } - if (i > 0 - && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) - != MessageEventModel::DelegateType::State // If it's not a state event - || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible - )) { - break; - } - } - - int excessAuthors; - if (uniqueAuthors.count() > 5) { - excessAuthors = uniqueAuthors.count() - 5; - } else { - excessAuthors = 0; - } - QString excessAuthorsString; - if (excessAuthors == 0) { - return QString(); - } else { - return QStringLiteral("+ %1").arg(excessAuthors); - } -} - -#include "moc_collapsestateproxymodel.cpp" diff --git a/src/models/collapsestateproxymodel.h b/src/models/collapsestateproxymodel.h deleted file mode 100644 index 82db9886e..000000000 --- a/src/models/collapsestateproxymodel.h +++ /dev/null @@ -1,80 +0,0 @@ -// SPDX-FileCopyrightText: 2022 Tobias Fella -// SPDX-License-Identifier: LGPL-2.0-or-later - -#pragma once - -#include "messageeventmodel.h" -#include - -/** - * @class CollapseStateProxyModel - * - * This model aggregates multiple sequential state events into a single entry. - * - * Events are only aggregated if they happened on the same day. - * - * @sa MessageEventModel - */ -class CollapseStateProxyModel : public QSortFilterProxyModel -{ - Q_OBJECT -public: - /** - * @brief Defines the model roles. - */ - enum Roles { - AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */ - StateEventsRole, /**< List of state events in the aggregated state. */ - AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */ - ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */ - LastRole, // Keep this last - }; - - /** - * @brief Whether a row should be shown out or not. - * - * @sa QSortFilterProxyModel::filterAcceptsRow - */ - [[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; - - /** - * @brief Get the given role value at the given index. - * - * @sa QSortFilterProxyModel::data - */ - [[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override; - - /** - * @brief Returns a mapping from Role enum values to role names. - * - * @sa Roles, QAbstractProxyModel::roleNames() - */ - [[nodiscard]] QHash roleNames() const override; - -private: - /** - * @brief Aggregation of the text of consecutive state events starting at row. - * - * If state events happen on different days they will be split into two aggregate - * events. - */ - [[nodiscard]] QString aggregateEventToString(int row) const; - - /** - * @brief Return a list of consecutive state events starting at row. - * - * If state events happen on different days they will be split into two aggregate - * events. - */ - [[nodiscard]] QVariantList stateEventsList(int row) const; - - /** - * @brief List of the first 5 unique authors for the aggregate state events starting at row. - */ - [[nodiscard]] QVariantList authorList(int row) const; - - /** - * @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row. - */ - [[nodiscard]] QString excessAuthors(int row) const; -}; diff --git a/src/models/mediamessagefiltermodel.h b/src/models/mediamessagefiltermodel.h index 9158aee97..865534c18 100644 --- a/src/models/mediamessagefiltermodel.h +++ b/src/models/mediamessagefiltermodel.h @@ -5,7 +5,7 @@ #include -#include "models/collapsestateproxymodel.h" +#include "models/messagefiltermodel.h" /** * @class MediaMessageFilterModel @@ -22,7 +22,7 @@ public: * @brief Defines the model roles. */ enum Roles { - SourceRole = CollapseStateProxyModel::LastRole + 1, /**< The mxc source URL for the item. */ + SourceRole = MessageFilterModel::LastRole + 1, /**< The mxc source URL for the item. */ TempSourceRole, /**< Source for the temporary content (either blurhash or mxc URL). */ TypeRole, /**< The type of the media (image or video). */ CaptionRole, /**< The caption for the item. */ diff --git a/src/models/messagefiltermodel.cpp b/src/models/messagefiltermodel.cpp index 4d9140d2e..c95921a57 100644 --- a/src/models/messagefiltermodel.cpp +++ b/src/models/messagefiltermodel.cpp @@ -3,6 +3,8 @@ #include "messagefiltermodel.h" +#include + #include "messageeventmodel.h" #include "neochatconfig.h" @@ -32,22 +34,192 @@ bool MessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sour { const QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent); + // Don't show redacted (i.e. deleted) messages. if (index.data(MessageEventModel::IsRedactedRole).toBool() && !NeoChatConfig::self()->showDeletedMessages()) { return false; } + // Don't show hidden or replaced messages. const int specialMarks = index.data(MessageEventModel::SpecialMarksRole).toInt(); if (specialMarks == EventStatus::Hidden || specialMarks == EventStatus::Replaced) { return false; } + // Don't show events with an unknown type. const auto eventType = index.data(MessageEventModel::DelegateTypeRole).toInt(); - if (eventType == MessageEventModel::Other) { return false; } + // Don't show state events that are not the first in a consecutive group on the + // same day as they will be grouped as a single delegate. + const bool notLastRow = sourceRow < sourceModel()->rowCount() - 1; + const bool previousEventIsState = + sourceModel()->data(sourceModel()->index(sourceRow + 1, 0), MessageEventModel::DelegateTypeRole) == MessageEventModel::DelegateType::State; + const bool newDay = sourceModel()->data(sourceModel()->index(sourceRow, 0), MessageEventModel::ShowSectionRole).toBool(); + if (notLastRow && previousEventIsState && !newDay) { + return false; + } + return true; } +QVariant MessageFilterModel::data(const QModelIndex &index, int role) const +{ + if (role == AggregateDisplayRole) { + return aggregateEventToString(mapToSource(index).row()); + } else if (role == StateEventsRole) { + return stateEventsList(mapToSource(index).row()); + } else if (role == AuthorListRole) { + return authorList(mapToSource(index).row()); + } else if (role == ExcessAuthorsRole) { + return excessAuthors(mapToSource(index).row()); + } + return sourceModel()->data(mapToSource(index), role); +} + +QHash MessageFilterModel::roleNames() const +{ + auto roles = sourceModel()->roleNames(); + roles[AggregateDisplayRole] = "aggregateDisplay"; + roles[StateEventsRole] = "stateEvents"; + roles[AuthorListRole] = "authorList"; + roles[ExcessAuthorsRole] = "excessAuthors"; + return roles; +} + +QString MessageFilterModel::aggregateEventToString(int sourceRow) const +{ + QStringList parts; + QVariantList uniqueAuthors; + for (int i = sourceRow; i >= 0; i--) { + parts += sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::GenericDisplayRole).toString(); + QVariant nextAuthor = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); + if (!uniqueAuthors.contains(nextAuthor)) { + uniqueAuthors.append(nextAuthor); + } + if (i > 0 + && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) + != MessageEventModel::DelegateType::State // If it's not a state event + || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible + )) { + break; + } + } + parts.sort(); // Sort them so that all identical events can be collected. + if (!parts.isEmpty()) { + QStringList chunks; + while (!parts.isEmpty()) { + chunks += QString(); + int count = 1; + auto part = parts.takeFirst(); + chunks.last() += part; + while (!parts.isEmpty() && parts.first() == part) { + parts.removeFirst(); + count++; + } + if (count > 1 && uniqueAuthors.length() == 1) { + chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count); + } + } + chunks.removeDuplicates(); + QString text = QStringLiteral(""); // There can be links in the event text so make sure all are styled. + // The author text is either "n users" if > 1 user or the matrix.to link to a single user. + QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length()) + : QStringLiteral("%3 ") + .arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(), + uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(), + uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped()); + text += userText; + text += chunks.takeFirst(); + + if (chunks.size() > 0) { + while (chunks.size() > 1) { + text += i18nc("[action 1], [action 2 and/or action 3]", ", "); + text += chunks.takeFirst(); + } + text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and "); + text += chunks.takeFirst(); + } + return text; + } else { + return {}; + } +} + +QVariantList MessageFilterModel::stateEventsList(int sourceRow) const +{ + QVariantList stateEvents; + for (int i = sourceRow; i >= 0; i--) { + auto nextState = QVariantMap{ + {QStringLiteral("author"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)}, + {QStringLiteral("authorDisplayName"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()}, + {QStringLiteral("text"), sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()}, + }; + stateEvents.append(nextState); + if (i > 0 + && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) + != MessageEventModel::DelegateType::State // If it's not a state event + || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible + )) { + break; + } + } + return stateEvents; +} + +QVariantList MessageFilterModel::authorList(int sourceRow) const +{ + QVariantList uniqueAuthors; + for (int i = sourceRow; i >= 0; i--) { + QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); + if (!uniqueAuthors.contains(nextAvatar)) { + uniqueAuthors.append(nextAvatar); + } + if (i > 0 + && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) + != MessageEventModel::DelegateType::State // If it's not a state event + || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible + )) { + break; + } + } + + if (uniqueAuthors.count() > 5) { + uniqueAuthors = uniqueAuthors.mid(0, 5); + } + return uniqueAuthors; +} + +QString MessageFilterModel::excessAuthors(int row) const +{ + QVariantList uniqueAuthors; + for (int i = row; i >= 0; i--) { + QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole); + if (!uniqueAuthors.contains(nextAvatar)) { + uniqueAuthors.append(nextAvatar); + } + if (i > 0 + && (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole) + != MessageEventModel::DelegateType::State // If it's not a state event + || sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible + )) { + break; + } + } + + int excessAuthors; + if (uniqueAuthors.count() > 5) { + excessAuthors = uniqueAuthors.count() - 5; + } else { + excessAuthors = 0; + } + QString excessAuthorsString; + if (excessAuthors == 0) { + return QString(); + } else { + return QStringLiteral("+ %1").arg(excessAuthors); + } +} + #include "moc_messagefiltermodel.cpp" diff --git a/src/models/messagefiltermodel.h b/src/models/messagefiltermodel.h index 890d24eb4..0649e75cb 100644 --- a/src/models/messagefiltermodel.h +++ b/src/models/messagefiltermodel.h @@ -5,21 +5,79 @@ #include +#include "messageeventmodel.h" + /** * @class MessageFilterModel * * This model filters out any messages that should be hidden. * * Deleted messages are only hidden if the user hasn't set them to be shown. + * + * The model also contains the roles and functions to support aggregating multiple + * consecutive state events into a single delegate. The state events must all happen + * on the same day to be aggregated. */ class MessageFilterModel : public QSortFilterProxyModel { Q_OBJECT public: + /** + * @brief Defines the model roles. + */ + enum Roles { + AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */ + StateEventsRole, /**< List of state events in the aggregated state. */ + AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */ + ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */ + LastRole, // Keep this last + }; + explicit MessageFilterModel(QObject *parent = nullptr); /** * @brief Custom filter function to remove hidden messages. */ bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override; + + /** + * @brief Get the given role value at the given index. + * + * @sa QSortFilterProxyModel::data + */ + [[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override; + + /** + * @brief Returns a mapping from Role enum values to role names. + * + * @sa Roles, QAbstractProxyModel::roleNames() + */ + [[nodiscard]] QHash roleNames() const override; + +private: + /** + * @brief Aggregation of the text of consecutive state events starting at row. + * + * If state events happen on different days they will be split into two aggregate + * events. + */ + [[nodiscard]] QString aggregateEventToString(int row) const; + + /** + * @brief Return a list of consecutive state events starting at row. + * + * If state events happen on different days they will be split into two aggregate + * events. + */ + [[nodiscard]] QVariantList stateEventsList(int row) const; + + /** + * @brief List of the first 5 unique authors for the aggregate state events starting at row. + */ + [[nodiscard]] QVariantList authorList(int row) const; + + /** + * @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row. + */ + [[nodiscard]] QString excessAuthors(int row) const; }; diff --git a/src/qml/Component/TimelineView.qml b/src/qml/Component/TimelineView.qml index c98be8f6f..4d89d7a51 100644 --- a/src/qml/Component/TimelineView.qml +++ b/src/qml/Component/TimelineView.qml @@ -47,19 +47,16 @@ QQC2.ScrollView { interactive: Kirigami.Settings.isMobile bottomMargin: Kirigami.Units.largeSpacing + Math.round(Kirigami.Theme.defaultFont.pointSize * 2) - model: collapseStateProxyModel + model: sortedMessageEventModel MessageEventModel { id: messageEventModel room: root.currentRoom } - CollapseStateProxyModel { - id: collapseStateProxyModel - sourceModel: MessageFilterModel { - id: sortedMessageEventModel - sourceModel: messageEventModel - } + MessageFilterModel { + id: sortedMessageEventModel + sourceModel: messageEventModel } Timer { @@ -349,7 +346,7 @@ QQC2.ScrollView { MediaMessageFilterModel { id: mediaMessageFilterModel - sourceModel: collapseStateProxyModel + sourceModel: sortedMessageEventModel } Component {