Foldable State Events
Implement folding sections for multiple consecutive state events. This also reworks some of the aggregated text output: - Only new days cause a new section - The aggregated text starts with either a single username or n users - If the same user did the same action mutltiple times it will be in the aggregated text a user did x n times - When there are multiple authors in a block with multiple state event types it will use or rather than and before the last event. e.g 3 user left the room or joined the room. Folded  Unfolded  Implements network/neochat#90
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "collapsestateproxymodel.h"
|
||||
#include "messageeventmodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -12,18 +13,17 @@ bool CollapseStateProxyModel::filterAcceptsRow(int source_row, const QModelIndex
|
||||
!= MessageEventModel::DelegateType::State // If this is not a state, show it
|
||||
|| sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= 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
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::EventResolvedTypeRole)
|
||||
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0),
|
||||
MessageEventModel::EventResolvedTypeRole) // Also show it if it's of a different type than the one before TODO improve in
|
||||
|| sourceModel()->data(sourceModel()->index(source_row, 0), MessageEventModel::AuthorIdRole)
|
||||
!= sourceModel()->data(sourceModel()->index(source_row + 1, 0), MessageEventModel::AuthorIdRole); // Also show it if it's a different author
|
||||
|| 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());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
@@ -32,27 +32,29 @@ QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||
{
|
||||
auto roles = sourceModel()->roleNames();
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
roles[StateEventsRole] = "stateEvents";
|
||||
roles[AuthorListRole] = "authorList";
|
||||
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), Qt::DisplayRole).toString();
|
||||
if (sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventTypeRole)
|
||||
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 (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| (i > 0
|
||||
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::EventResolvedTypeRole)
|
||||
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventResolvedTypeRole)) // or of a different type
|
||||
|| (i > 0
|
||||
&& sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorIdRole)
|
||||
!= sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::AuthorIdRole)) // or by a different author
|
||||
|| 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()) {
|
||||
@@ -64,18 +66,27 @@ QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1) {
|
||||
chunks.last() += i18ncp("[user did something] n times", " %1 time", " %1 times", count);
|
||||
if (count > 1 && uniqueAuthors.length() == 1) {
|
||||
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
|
||||
}
|
||||
}
|
||||
QString text = chunks.takeFirst();
|
||||
chunks.removeDuplicates();
|
||||
QString text = "<style>a {text-decoration: none;}</style>"; // 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("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()["id"].toString(),
|
||||
uniqueAuthors[0].toMap()["color"].toString(),
|
||||
uniqueAuthors[0].toMap()["displayName"].toString());
|
||||
text += userText;
|
||||
text += chunks.takeFirst();
|
||||
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and action 3]", ", ");
|
||||
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
}
|
||||
text += i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
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;
|
||||
@@ -83,3 +94,41 @@ QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
||||
{
|
||||
QVariantList stateEvents;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
auto nextState = QVariantMap{
|
||||
{"author", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||
{"authorDisplayName", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= 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 (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::EventTypeRole)
|
||||
!= 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 uniqueAuthors;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user