Add a slow way to search a room

This commit is contained in:
Nicolas Werner 2022-10-06 21:59:59 +02:00
parent 857d9cf2b6
commit f1c1f18f81
No known key found for this signature in database
GPG key ID: C8D75E610773F2D9
8 changed files with 120 additions and 48 deletions

View file

@ -22,6 +22,8 @@ Item {
property int availableWidth: width property int availableWidth: width
property string searchString: ""
ScrollBar { ScrollBar {
id: scrollbar id: scrollbar
parent: chat.parent parent: chat.parent
@ -43,9 +45,10 @@ Item {
id: filteredTimeline id: filteredTimeline
source: room source: room
filterByThread: room ? room.thread : "" filterByThread: room ? room.thread : ""
filterByContent: chatRoot.searchString
} }
model: filteredTimeline.filterByThread ? filteredTimeline : room model: (filteredTimeline.filterByThread || filteredTimeline.filterByContent) ? filteredTimeline : room
// reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107 // reuseItems still has a few bugs, see https://bugreports.qt.io/browse/QTBUG-95105 https://bugreports.qt.io/browse/QTBUG-95107
//onModelChanged: if (room) room.sendReset() //onModelChanged: if (room) room.sendReset()
//reuseItems: true //reuseItems: true
@ -403,7 +406,7 @@ Item {
required property bool isEditable required property bool isEditable
required property bool isEdited required property bool isEdited
required property bool isStateEvent required property bool isStateEvent
required property bool previousMessageIsStateEvent property bool previousMessageIsStateEvent: chat.model.dataByIndex(index+1, Room.IsStateEvent)
required property string replyTo required property string replyTo
required property string threadId required property string threadId
required property string userId required property string userId
@ -417,9 +420,9 @@ Item {
required property int status required property int status
required property int index required property int index
required property int relatedEventCacheBuster required property int relatedEventCacheBuster
required property string previousMessageUserId
required property string day required property string day
required property string previousMessageDay property string previousMessageUserId: chat.model.dataByIndex(index+1, Room.UserId)
property string previousMessageDay: chat.model.dataByIndex(index+1, Room.Day)
required property string userName required property string userName
property bool scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY) property bool scrolledToThis: eventId === room.scrollTarget && (y + height > chat.y + chat.contentY && y < chat.y + chat.height + chat.contentY)

View file

@ -66,6 +66,8 @@ Item {
spacing: 0 spacing: 0
TopBar { TopBar {
id: topBar
showBackButton: timelineView.showBackButton showBackButton: timelineView.showBackButton
} }
@ -102,6 +104,7 @@ Item {
MessageView { MessageView {
implicitHeight: msgView.height - typingIndicator.height implicitHeight: msgView.height - typingIndicator.height
searchString: topBar.searchString
Layout.fillWidth: true Layout.fillWidth: true
} }

View file

@ -25,6 +25,14 @@ Pane {
property bool isDirect: room ? room.isDirect : false property bool isDirect: room ? room.isDirect : false
property string directChatOtherUserId: room ? room.directChatOtherUserId : "" property string directChatOtherUserId: room ? room.directChatOtherUserId : ""
property string searchString: ""
onRoomIdChanged: {
searchString = "";
searchButton.searchActive = false;
searchField.text = ""
}
Layout.fillWidth: true Layout.fillWidth: true
implicitHeight: topLayout.height + Nheko.paddingMedium * 2 implicitHeight: topLayout.height + Nheko.paddingMedium * 2
z: 3 z: 3
@ -177,12 +185,42 @@ Pane {
text: roomTopic text: roomTopic
} }
AbstractButton { ImageButton {
id: pinButton
property bool pinsShown: !Settings.hiddenPins.includes(roomId)
visible: !!room && room.pinnedMessages.length > 0
Layout.column: 3 Layout.column: 3
Layout.row: 1 Layout.row: 1
Layout.rowSpan: 2 Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg"
ToolTip.visible: hovered
ToolTip.text: qsTr("Show or hide pinned messages")
onClicked: {
var ps = Settings.hiddenPins;
if (pinsShown) {
ps.push(roomId);
} else {
const index = ps.indexOf(roomId);
if (index > -1) {
ps.splice(index, 1);
}
}
Settings.hiddenPins = ps;
}
}
AbstractButton {
Layout.column: 4
Layout.row: 1
Layout.rowSpan: 2
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.maximumWidth: Nheko.avatarSize - Nheko.paddingMedium
contentItem: EncryptionIndicator { contentItem: EncryptionIndicator {
sourceSize.height: parent.Layout.preferredHeight * Screen.devicePixelRatio sourceSize.height: parent.Layout.preferredHeight * Screen.devicePixelRatio
@ -216,40 +254,37 @@ Pane {
} }
ImageButton { ImageButton {
id: pinButton id: searchButton
property bool pinsShown: !Settings.hiddenPins.includes(roomId) property bool searchActive: false
visible: !!room && room.pinnedMessages.length > 0 visible: !!room
Layout.column: 4 Layout.column: 5
Layout.row: 1 Layout.row: 1
Layout.rowSpan: 2 Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredHeight: Nheko.avatarSize - Nheko.paddingMedium
Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium Layout.preferredWidth: Nheko.avatarSize - Nheko.paddingMedium
image: pinsShown ? ":/icons/icons/ui/pin.svg" : ":/icons/icons/ui/pin-off.svg" image: ":/icons/icons/ui/search.svg"
ToolTip.visible: hovered ToolTip.visible: hovered
ToolTip.text: qsTr("Show or hide pinned messages") ToolTip.text: qsTr("Search this room")
onClicked: { onClicked: {
var ps = Settings.hiddenPins; searchActive = !searchActive
if (pinsShown) { if (searchActive) {
ps.push(roomId); searchField.forceActiveFocus();
} else { }
const index = ps.indexOf(roomId); else {
if (index > -1) { searchField.clear();
ps.splice(index, 1); topBar.searchString = "";
} }
} }
Settings.hiddenPins = ps;
}
} }
ImageButton { ImageButton {
id: roomOptionsButton id: roomOptionsButton
visible: !!room visible: !!room
Layout.column: 5 Layout.column: 6
Layout.row: 1 Layout.row: 1
Layout.rowSpan: 2 Layout.rowSpan: 2
Layout.alignment: Qt.AlignVCenter Layout.alignment: Qt.AlignVCenter
@ -293,7 +328,7 @@ Pane {
Layout.row: 3 Layout.row: 3
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 3 Layout.columnSpan: 4
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4) Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 4)
@ -374,7 +409,7 @@ Pane {
Layout.row: 4 Layout.row: 4
Layout.column: 2 Layout.column: 2
Layout.columnSpan: 1 Layout.columnSpan: 4
Layout.fillWidth: true Layout.fillWidth: true
Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5) Layout.preferredHeight: Math.min(contentHeight, Nheko.avatarSize * 1.5)
@ -404,6 +439,20 @@ Pane {
} }
} }
} }
MatrixTextField {
id: searchField
visible: searchButton.searchActive
Layout.row: 5
Layout.column: 2
Layout.columnSpan: 4
Layout.fillWidth: true
placeholderText: qsTr("Enter search query")
onAccepted: topBar.searchString = text
}
} }
CursorShape { CursorShape {

View file

@ -898,6 +898,7 @@ EventStore::fetchMore()
mtx::http::MessagesOpts opts; mtx::http::MessagesOpts opts;
opts.room_id = room_id_; opts.room_id = room_id_;
opts.from = cache::client()->previousBatchToken(room_id_); opts.from = cache::client()->previousBatchToken(room_id_);
opts.limit = 80;
nhlog::ui()->debug("Paginating room {}, token {}", opts.room_id, opts.from); nhlog::ui()->debug("Paginating room {}, token {}", opts.room_id, opts.from);

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2022 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "TimelineFilter.h" #include "TimelineFilter.h"
#include "Logging.h" #include "Logging.h"
@ -19,6 +23,17 @@ TimelineFilter::setThreadId(const QString &t)
emit threadIdChanged(); emit threadIdChanged();
} }
void
TimelineFilter::setContentFilter(const QString &c)
{
nhlog::ui()->debug("Filtering by content '{}'", c.toStdString());
if (this->contentFilter != c) {
this->contentFilter = c;
invalidateFilter();
}
emit contentFilterChanged();
}
void void
TimelineFilter::setSource(TimelineModel *s) TimelineFilter::setSource(TimelineModel *s)
{ {
@ -62,11 +77,20 @@ TimelineFilter::currentIndex() const
bool bool
TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const TimelineFilter::filterAcceptsRow(int source_row, const QModelIndex &) const
{ {
if (threadId.isEmpty()) if (threadId.isEmpty() && contentFilter.isEmpty())
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)
.toString()
.contains(contentFilter, Qt::CaseInsensitive)) {
return false;
}
if (threadId.isEmpty())
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;
} else { } else {

View file

@ -16,6 +16,8 @@ class TimelineFilter : public QSortFilterProxyModel
Q_OBJECT Q_OBJECT
Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged) Q_PROPERTY(QString filterByThread READ filterByThread WRITE setThreadId NOTIFY threadIdChanged)
Q_PROPERTY(QString filterByContent READ filterByContent WRITE setContentFilter NOTIFY
contentFilterChanged)
Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged) Q_PROPERTY(TimelineModel *source READ source WRITE setSource NOTIFY sourceChanged)
Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged) Q_PROPERTY(int currentIndex READ currentIndex WRITE setCurrentIndex NOTIFY currentIndexChanged)
@ -23,15 +25,23 @@ public:
explicit TimelineFilter(QObject *parent = nullptr); explicit TimelineFilter(QObject *parent = nullptr);
QString filterByThread() const { return threadId; } QString filterByThread() const { return threadId; }
QString filterByContent() const { return contentFilter; }
TimelineModel *source() const; TimelineModel *source() const;
int currentIndex() const; int currentIndex() const;
void setThreadId(const QString &t); void setThreadId(const QString &t);
void setContentFilter(const QString &t);
void setSource(TimelineModel *t); void setSource(TimelineModel *t);
void setCurrentIndex(int idx); void setCurrentIndex(int idx);
Q_INVOKABLE QVariant dataByIndex(int i, int role = Qt::DisplayRole) const
{
return data(index(i, 0), role);
}
signals: signals:
void threadIdChanged(); void threadIdChanged();
void contentFilterChanged();
void sourceChanged(); void sourceChanged();
void currentIndexChanged(); void currentIndexChanged();
@ -39,5 +49,5 @@ protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private: private:
QString threadId; QString threadId, contentFilter;
}; };

View file

@ -481,12 +481,9 @@ TimelineModel::roleNames() const
{IsOnlyEmoji, "isOnlyEmoji"}, {IsOnlyEmoji, "isOnlyEmoji"},
{Body, "body"}, {Body, "body"},
{FormattedBody, "formattedBody"}, {FormattedBody, "formattedBody"},
{PreviousMessageUserId, "previousMessageUserId"},
{IsSender, "isSender"}, {IsSender, "isSender"},
{UserId, "userId"}, {UserId, "userId"},
{UserName, "userName"}, {UserName, "userName"},
{PreviousMessageDay, "previousMessageDay"},
{PreviousMessageIsStateEvent, "previousMessageIsStateEvent"},
{Day, "day"}, {Day, "day"},
{Timestamp, "timestamp"}, {Timestamp, "timestamp"},
{Url, "url"}, {Url, "url"},
@ -804,22 +801,6 @@ TimelineModel::data(const QModelIndex &index, int role) const
if (!event) if (!event)
return ""; return "";
if (role == PreviousMessageDay || role == PreviousMessageUserId ||
role == PreviousMessageIsStateEvent) {
int prevIdx = rowCount() - index.row() - 2;
if (prevIdx < 0)
return {};
auto tempEv = events.get(prevIdx);
if (!tempEv)
return {};
if (role == PreviousMessageUserId)
return data(*tempEv, UserId);
else if (role == PreviousMessageDay)
return data(*tempEv, Day);
else
return data(*tempEv, IsStateEvent);
}
return data(*event, role); return data(*event, role);
} }

View file

@ -214,12 +214,9 @@ public:
IsOnlyEmoji, IsOnlyEmoji,
Body, Body,
FormattedBody, FormattedBody,
PreviousMessageUserId,
IsSender, IsSender,
UserId, UserId,
UserName, UserName,
PreviousMessageDay,
PreviousMessageIsStateEvent,
Day, Day,
Timestamp, Timestamp,
Url, Url,
@ -257,6 +254,10 @@ public:
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const; QVariant data(const mtx::events::collections::TimelineEvents &event, int role) const;
Q_INVOKABLE QVariant dataById(const QString &id, int role, const QString &relatedTo); Q_INVOKABLE QVariant dataById(const QString &id, int role, const QString &relatedTo);
Q_INVOKABLE QVariant dataByIndex(int i, int role = Qt::DisplayRole) const
{
return data(index(i), role);
}
bool canFetchMore(const QModelIndex &) const override; bool canFetchMore(const QModelIndex &) const override;
void fetchMore(const QModelIndex &) override; void fetchMore(const QModelIndex &) override;