Allow filtering the timeline for notifications

This commit is contained in:
Nicolas Werner 2025-09-13 01:01:06 +02:00
parent 5b025fa2b0
commit 53cd31d181
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
7 changed files with 73 additions and 11 deletions

View file

@ -0,0 +1 @@
<svg width="32" height="32" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12 1.996a7.49 7.49 0 0 1 7.496 7.25l.004.25v4.097l1.38 3.156a1.25 1.25 0 0 1-1.145 1.75L15 18.502a3 3 0 0 1-5.995.177L9 18.499H4.275a1.251 1.251 0 0 1-1.147-1.747L4.5 13.594V9.496c0-4.155 3.352-7.5 7.5-7.5ZM13.5 18.5l-3 .002a1.5 1.5 0 0 0 2.993.145l.006-.147ZM12 3.496c-3.32 0-6 2.674-6 6v4.41L4.656 17h14.697L18 13.907V9.509l-.004-.225A5.988 5.988 0 0 0 12 3.496Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 494 B

View file

@ -16,6 +16,7 @@ Item {
property int availableWidth: width property int availableWidth: width
property int padding: Nheko.paddingMedium property int padding: Nheko.paddingMedium
property string searchString: "" property string searchString: ""
property bool filterByNotifications: false
property Room roommodel: room property Room roommodel: room
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu // HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
@ -60,7 +61,7 @@ Item {
boundsBehavior: Flickable.StopAtBounds boundsBehavior: Flickable.StopAtBounds
displayMarginBeginning: height / 4 displayMarginBeginning: height / 4
displayMarginEnd: height / 4 displayMarginEnd: height / 4
model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent || filteredTimeline.filterByNotifications) ? filteredTimeline : room
//pixelAligned: true //pixelAligned: true
spacing: 2 spacing: 2
verticalLayoutDirection: ListView.BottomToTop verticalLayoutDirection: ListView.BottomToTop
@ -145,6 +146,7 @@ Item {
id: filteredTimeline id: filteredTimeline
filterByContent: chatRoot.searchString filterByContent: chatRoot.searchString
filterByNotifications: chatRoot.filterByNotifications
filterByThread: room ? room.thread : "" filterByThread: room ? room.thread : ""
source: room source: room
} }

View file

@ -119,6 +119,7 @@ Item {
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: msgView.height - typingIndicator.height implicitHeight: msgView.height - typingIndicator.height
searchString: topBar.searchString searchString: topBar.searchString
filterByNotifications: topBar.filterNotifications
} }
Loader { Loader {
source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? (Qt.platform.os != "windows" ? "voip/VideoCall.qml" : "voip/VideoCallD3D11.qml") : "" source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? (Qt.platform.os != "windows" ? "voip/VideoCall.qml" : "voip/VideoCallD3D11.qml") : ""

View file

@ -22,6 +22,7 @@ Pane {
property bool searchHasFocus: searchField.focus && searchField.enabled property bool searchHasFocus: searchField.focus && searchField.enabled
property string searchString: "" property string searchString: ""
property bool showBackButton: false property bool showBackButton: false
property bool filterNotifications: false
property int trustlevel: room ? room.trustlevel : Crypto.Unverified property int trustlevel: room ? room.trustlevel : Crypto.Unverified
Layout.fillWidth: true Layout.fillWidth: true
@ -129,13 +130,30 @@ Pane {
selectByMouse: false selectByMouse: false
text: roomTopic text: roomTopic
} }
ImageButton {
id: notificationsButton
Layout.alignment: Qt.AlignRight
Layout.column: 3
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1
Layout.rowSpan: 2
ToolTip.text: qsTr("Show only notifications")
ToolTip.visible: hovered
image: ":/icons/icons/ui/alert.svg"
onClicked: {
topBar.filterNotifications = !topBar.filterNotifications
}
}
ImageButton { ImageButton {
id: pinButton id: pinButton
property bool pinsShown: !Settings.hiddenPins.includes(roomId) property bool pinsShown: !Settings.hiddenPins.includes(roomId)
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 3 Layout.column: 4
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1 Layout.row: 1
@ -160,7 +178,7 @@ Pane {
} }
AbstractButton { AbstractButton {
id: memberButton id: memberButton
Layout.column: 4 Layout.column: 5
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1 Layout.row: 1
@ -200,7 +218,7 @@ Pane {
property bool searchActive: false property bool searchActive: false
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 5 Layout.column: 6
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1 Layout.row: 1
@ -224,7 +242,7 @@ Pane {
id: roomOptionsButton id: roomOptionsButton
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.column: 6 Layout.column: 7
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1 Layout.row: 1
@ -273,7 +291,7 @@ Pane {
id: pinnedMessages id: pinnedMessages
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 4 Layout.columnSpan: 5
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4) Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
Layout.row: 3 Layout.row: 3
@ -330,7 +348,7 @@ Pane {
id: widgets id: widgets
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 4 Layout.columnSpan: 5
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5) Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
Layout.row: 4 Layout.row: 4
@ -356,7 +374,7 @@ Pane {
id: searchField id: searchField
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 4 Layout.columnSpan: 5
Layout.fillWidth: true Layout.fillWidth: true
Layout.row: 5 Layout.row: 5
enabled: visible enabled: visible
@ -378,6 +396,7 @@ Pane {
searchString = ""; searchString = "";
searchButton.searchActive = false; searchButton.searchActive = false;
searchField.text = ""; searchField.text = "";
filterNotifications = false;
} }
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu // HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu

View file

@ -1,6 +1,7 @@
<RCC> <RCC>
<qresource prefix="/icons"> <qresource prefix="/icons">
<file>icons/ui/add-square-button.svg</file> <file>icons/ui/add-square-button.svg</file>
<file>icons/ui/alert.svg</file>
<file>icons/ui/angle-arrow-left.svg</file> <file>icons/ui/angle-arrow-left.svg</file>
<file>icons/ui/attach.svg</file> <file>icons/ui/attach.svg</file>
<file>icons/ui/ban.svg</file> <file>icons/ui/ban.svg</file>

View file

@ -96,6 +96,10 @@ TimelineFilter::setThreadId(const QString &t)
{ {
nhlog::ui()->debug("Filtering by thread '{}'", t.toStdString()); nhlog::ui()->debug("Filtering by thread '{}'", t.toStdString());
if (this->threadId != t) { if (this->threadId != t) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
beginFilterChange();
#endif
this->threadId = t; this->threadId = t;
emit threadIdChanged(); emit threadIdChanged();
@ -104,11 +108,30 @@ TimelineFilter::setThreadId(const QString &t)
} }
} }
void
TimelineFilter::setFilterNotifications(bool filter)
{
nhlog::ui()->debug("Filtering by notifications '{}'", filter);
if (this->filterByNotifications_ != filter) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
beginFilterChange();
#endif
this->filterByNotifications_ = filter;
emit filterNotificationsChanged();
startFiltering();
fetchMore({});
}
}
void void
TimelineFilter::setContentFilter(const QString &c) TimelineFilter::setContentFilter(const QString &c)
{ {
nhlog::ui()->debug("Filtering by content '{}'", c.toStdString()); nhlog::ui()->debug("Filtering by content '{}'", c.toStdString());
if (this->contentFilter != c) { if (this->contentFilter != c) {
#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
beginFilterChange();
#endif
this->contentFilter = c; this->contentFilter = c;
emit contentFilterChanged(); emit contentFilterChanged();
@ -145,7 +168,8 @@ TimelineFilter::sourceDataChanged(const QModelIndex &topLeft,
const QModelIndex &bottomRight, const QModelIndex &bottomRight,
const QVector<int> &roles) const QVector<int> &roles)
{ {
if (!roles.contains(TimelineModel::Roles::Body) && !roles.contains(TimelineModel::ThreadId)) if (!roles.contains(TimelineModel::Roles::Body) && !roles.contains(TimelineModel::ThreadId) &&
!roles.contains(TimelineModel::Notificationlevel))
return; return;
if (auto s = source()) { if (auto s = source()) {
@ -233,19 +257,27 @@ TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const
if (source_row > incrementalSearchIndex) if (source_row > incrementalSearchIndex)
return false; return false;
if (threadId.isEmpty() && contentFilter.isEmpty()) if (threadId.isEmpty() && contentFilter.isEmpty() && !filterByNotifications_)
return true; return true;
if (auto s = sourceModel()) { if (auto s = sourceModel()) {
auto idx = s->index(source_row, 0); auto idx = s->index(source_row, 0);
if (!contentFilter.isEmpty() && !s->data(idx, TimelineModel::Body) if (!contentFilter.isEmpty() && !s->data(idx, TimelineModel::Body)
.toString() .toString()
.contains(contentFilter, Qt::CaseInsensitive)) { .contains(contentFilter, Qt::CaseInsensitive)) {
return false; return false;
} }
if (threadId.isEmpty()) if (filterByNotifications_ && s->data(idx, TimelineModel::Notificationlevel)
.value<qml_mtx_events::NotificationLevel>() !=
qml_mtx_events::NotificationLevel::Highlight) {
return false;
}
if (threadId.isEmpty()) {
return true; return true;
}
return s->data(idx, TimelineModel::EventId) == threadId || return s->data(idx, TimelineModel::EventId) == threadId ||
s->data(idx, TimelineModel::ThreadId) == threadId; s->data(idx, TimelineModel::ThreadId) == threadId;

View file

@ -18,6 +18,8 @@ class TimelineFilter : public QSortFilterProxyModel
QML_ELEMENT QML_ELEMENT
Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged) Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged)
Q_PROPERTY(bool filterByNotifications READ filterByNotifications WRITE setFilterNotifications
NOTIFY filterNotificationsChanged)
Q_PROPERTY(QString filterByContent READ filterByContent WRITE setContentFilter NOTIFY Q_PROPERTY(QString filterByContent READ filterByContent WRITE setContentFilter NOTIFY
contentFilterChanged) contentFilterChanged)
Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged)
@ -28,12 +30,14 @@ public:
explicit TimelineFilter(QObject *parent = nullptr); explicit TimelineFilter(QObject *parent = nullptr);
QString filterByThread() const { return threadId; } QString filterByThread() const { return threadId; }
bool filterByNotifications() const { return filterByNotifications_; }
QString filterByContent() const { return contentFilter; } QString filterByContent() const { return contentFilter; }
TimelineModel *source() const; TimelineModel *source() const;
int currentIndex() const; int currentIndex() const;
bool isFiltering() const; bool isFiltering() const;
void setThreadId(const QString &t); void setThreadId(const QString &t);
void setFilterNotifications(bool v);
void setContentFilter(const QString &t); void setContentFilter(const QString &t);
void setSource(TimelineModel *t); void setSource(TimelineModel *t);
void setCurrentIndex(int idx); void setCurrentIndex(int idx);
@ -47,6 +51,7 @@ public:
signals: signals:
void threadIdChanged(); void threadIdChanged();
void filterNotificationsChanged();
void contentFilterChanged(); void contentFilterChanged();
void sourceChanged(); void sourceChanged();
void currentIndexChanged(); void currentIndexChanged();
@ -67,4 +72,5 @@ private:
QString threadId, contentFilter; QString threadId, contentFilter;
int cachedCount = 0, incrementalSearchIndex = 0; int cachedCount = 0, incrementalSearchIndex = 0;
bool filterByNotifications_ = false;
}; };