diff --git a/resources/icons/ui/alert.svg b/resources/icons/ui/alert.svg
new file mode 100644
index 00000000..7b730a59
--- /dev/null
+++ b/resources/icons/ui/alert.svg
@@ -0,0 +1 @@
+
diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml
index 8a457afb..0c71573a 100644
--- a/resources/qml/MessageView.qml
+++ b/resources/qml/MessageView.qml
@@ -16,6 +16,7 @@ Item {
property int availableWidth: width
property int padding: Nheko.paddingMedium
property string searchString: ""
+ property bool filterByNotifications: false
property Room roommodel: room
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
@@ -60,7 +61,7 @@ Item {
boundsBehavior: Flickable.StopAtBounds
displayMarginBeginning: height / 4
displayMarginEnd: height / 4
- model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
+ model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent || filteredTimeline.filterByNotifications) ? filteredTimeline : room
//pixelAligned: true
spacing: 2
verticalLayoutDirection: ListView.BottomToTop
@@ -145,6 +146,7 @@ Item {
id: filteredTimeline
filterByContent: chatRoot.searchString
+ filterByNotifications: chatRoot.filterByNotifications
filterByThread: room ? room.thread : ""
source: room
}
diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml
index 8e83cc1f..64493c4e 100644
--- a/resources/qml/TimelineView.qml
+++ b/resources/qml/TimelineView.qml
@@ -119,6 +119,7 @@ Item {
Layout.fillWidth: true
implicitHeight: msgView.height - typingIndicator.height
searchString: topBar.searchString
+ filterByNotifications: topBar.filterNotifications
}
Loader {
source: CallManager.isOnCall && CallManager.callType != Voip.VOICE ? (Qt.platform.os != "windows" ? "voip/VideoCall.qml" : "voip/VideoCallD3D11.qml") : ""
diff --git a/resources/qml/TopBar.qml b/resources/qml/TopBar.qml
index 0bdd4ab8..900e59e8 100644
--- a/resources/qml/TopBar.qml
+++ b/resources/qml/TopBar.qml
@@ -22,6 +22,7 @@ Pane {
property bool searchHasFocus: searchField.focus && searchField.enabled
property string searchString: ""
property bool showBackButton: false
+ property bool filterNotifications: false
property int trustlevel: room ? room.trustlevel : Crypto.Unverified
Layout.fillWidth: true
@@ -129,13 +130,30 @@ Pane {
selectByMouse: false
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 {
id: pinButton
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
Layout.alignment: Qt.AlignVCenter
- Layout.column: 3
+ Layout.column: 4
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1
@@ -160,7 +178,7 @@ Pane {
}
AbstractButton {
id: memberButton
- Layout.column: 4
+ Layout.column: 5
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1
@@ -200,7 +218,7 @@ Pane {
property bool searchActive: false
Layout.alignment: Qt.AlignVCenter
- Layout.column: 5
+ Layout.column: 6
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1
@@ -224,7 +242,7 @@ Pane {
id: roomOptionsButton
Layout.alignment: Qt.AlignVCenter
- Layout.column: 6
+ Layout.column: 7
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
Layout.row: 1
@@ -273,7 +291,7 @@ Pane {
id: pinnedMessages
Layout.column: 2
- Layout.columnSpan: 4
+ Layout.columnSpan: 5
Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
Layout.row: 3
@@ -330,7 +348,7 @@ Pane {
id: widgets
Layout.column: 2
- Layout.columnSpan: 4
+ Layout.columnSpan: 5
Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
Layout.row: 4
@@ -356,7 +374,7 @@ Pane {
id: searchField
Layout.column: 2
- Layout.columnSpan: 4
+ Layout.columnSpan: 5
Layout.fillWidth: true
Layout.row: 5
enabled: visible
@@ -378,6 +396,7 @@ Pane {
searchString = "";
searchButton.searchActive = false;
searchField.text = "";
+ filterNotifications = false;
}
// HACK: https://bugreports.qt.io/browse/QTBUG-83972, qtwayland cannot auto hide menu
diff --git a/resources/res.qrc b/resources/res.qrc
index 642bc220..13d4c371 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -1,6 +1,7 @@
icons/ui/add-square-button.svg
+ icons/ui/alert.svg
icons/ui/angle-arrow-left.svg
icons/ui/attach.svg
icons/ui/ban.svg
diff --git a/src/timeline/TimelineFilter.cpp b/src/timeline/TimelineFilter.cpp
index 0833900e..8e323ff6 100644
--- a/src/timeline/TimelineFilter.cpp
+++ b/src/timeline/TimelineFilter.cpp
@@ -96,6 +96,10 @@ TimelineFilter::setThreadId(const QString &t)
{
nhlog::ui()->debug("Filtering by thread '{}'", t.toStdString());
if (this->threadId != t) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
+ beginFilterChange();
+#endif
+
this->threadId = t;
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
TimelineFilter::setContentFilter(const QString &c)
{
nhlog::ui()->debug("Filtering by content '{}'", c.toStdString());
if (this->contentFilter != c) {
+#if QT_VERSION >= QT_VERSION_CHECK(6, 9, 0)
+ beginFilterChange();
+#endif
this->contentFilter = c;
emit contentFilterChanged();
@@ -145,7 +168,8 @@ TimelineFilter::sourceDataChanged(const QModelIndex &topLeft,
const QModelIndex &bottomRight,
const QVector &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;
if (auto s = source()) {
@@ -233,19 +257,27 @@ TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const
if (source_row > incrementalSearchIndex)
return false;
- if (threadId.isEmpty() && contentFilter.isEmpty())
+ if (threadId.isEmpty() && contentFilter.isEmpty() && !filterByNotifications_)
return true;
if (auto s = sourceModel()) {
auto idx = s->index(source_row, 0);
+
if (!contentFilter.isEmpty() && !s->data(idx, TimelineModel::Body)
.toString()
.contains(contentFilter, Qt::CaseInsensitive)) {
return false;
}
- if (threadId.isEmpty())
+ if (filterByNotifications_ && s->data(idx, TimelineModel::Notificationlevel)
+ .value() !=
+ qml_mtx_events::NotificationLevel::Highlight) {
+ return false;
+ }
+
+ if (threadId.isEmpty()) {
return true;
+ }
return s->data(idx, TimelineModel::EventId) == threadId ||
s->data(idx, TimelineModel::ThreadId) == threadId;
diff --git a/src/timeline/TimelineFilter.h b/src/timeline/TimelineFilter.h
index 336339e2..76d5bf52 100644
--- a/src/timeline/TimelineFilter.h
+++ b/src/timeline/TimelineFilter.h
@@ -18,6 +18,8 @@ class TimelineFilter : public QSortFilterProxyModel
QML_ELEMENT
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
contentFilterChanged)
Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged)
@@ -28,12 +30,14 @@ public:
explicit TimelineFilter(QObject *parent = nullptr);
QString filterByThread() const { return threadId; }
+ bool filterByNotifications() const { return filterByNotifications_; }
QString filterByContent() const { return contentFilter; }
TimelineModel *source() const;
int currentIndex() const;
bool isFiltering() const;
void setThreadId(const QString &t);
+ void setFilterNotifications(bool v);
void setContentFilter(const QString &t);
void setSource(TimelineModel *t);
void setCurrentIndex(int idx);
@@ -47,6 +51,7 @@ public:
signals:
void threadIdChanged();
+ void filterNotificationsChanged();
void contentFilterChanged();
void sourceChanged();
void currentIndexChanged();
@@ -67,4 +72,5 @@ private:
QString threadId, contentFilter;
int cachedCount = 0, incrementalSearchIndex = 0;
+ bool filterByNotifications_ = false;
};