Merge pull request #464 from monich/filter

Improve chat search performance
This commit is contained in:
Sebastian Wolf 2021-12-09 22:11:05 +01:00 committed by GitHub
commit 37518d06a6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 178 additions and 28 deletions

View file

@ -38,6 +38,7 @@ SOURCES += src/harbour-fernschreiber.cpp \
src/tdlibfile.cpp \ src/tdlibfile.cpp \
src/tdlibreceiver.cpp \ src/tdlibreceiver.cpp \
src/tdlibwrapper.cpp \ src/tdlibwrapper.cpp \
src/textfiltermodel.cpp \
src/tgsplugin.cpp src/tgsplugin.cpp
DISTFILES += qml/harbour-fernschreiber.qml \ DISTFILES += qml/harbour-fernschreiber.qml \
@ -228,6 +229,7 @@ HEADERS += \
src/tdlibreceiver.h \ src/tdlibreceiver.h \
src/tdlibsecrets.h \ src/tdlibsecrets.h \
src/tdlibwrapper.h \ src/tdlibwrapper.h \
src/textfiltermodel.h \
src/tgsplugin.h src/tgsplugin.h
# https://github.com/Samsung/rlottie.git # https://github.com/Samsung/rlottie.git

View file

@ -103,19 +103,11 @@ Page {
} }
} }
Timer { TextFilterModel {
id: searchChatTimer id: chatListProxyModel
interval: 300 sourceModel: (chatSearchField.opacity > 0) ? chatListModel : null
running: false filterRoleName: "filter"
repeat: false filterText: chatSearchField.text
onTriggered: {
if (chatSearchField.text === "") {
chatListView.model = chatListModel;
} else {
chatListView.model = chatListProxyModel;
}
chatListProxyModel.setFilterWildcard("*" + chatSearchField.text + "*");
}
} }
function openMessage(chatId, messageId) { function openMessage(chatId, messageId) {
@ -355,10 +347,6 @@ Page {
placeholderText: qsTr("Filter your chats...") placeholderText: qsTr("Filter your chats...")
canHide: text === "" canHide: text === ""
onTextChanged: {
searchChatTimer.restart();
}
onHideClicked: { onHideClicked: {
resetFocus(); resetFocus();
} }
@ -380,7 +368,7 @@ Page {
clip: true clip: true
opacity: (overviewPage.chatListCreated && !overviewPage.logoutLoading) ? 1 : 0 opacity: (overviewPage.chatListCreated && !overviewPage.logoutLoading) ? 1 : 0
Behavior on opacity { FadeAnimation {} } Behavior on opacity { FadeAnimation {} }
model: chatListModel model: chatListProxyModel.sourceModel ? chatListProxyModel : chatListModel
delegate: ChatListViewItem { delegate: ChatListViewItem {
ownUserId: overviewPage.ownUserId ownUserId: overviewPage.ownUserId
isVerified: is_verified isVerified: is_verified
@ -394,7 +382,7 @@ Page {
ViewPlaceholder { ViewPlaceholder {
enabled: chatListView.count === 0 enabled: chatListView.count === 0
text: chatSearchField.text === "" ? qsTr("You don't have any chats yet.") : qsTr("No matching chats found.") text: chatListModel.count === 0 ? qsTr("You don't have any chats yet.") : qsTr("No matching chats found.")
hintText: qsTr("You can search public chats or create a new chat via the pull-down menu.") hintText: qsTr("You can search public chats or create a new chat via the pull-down menu.")
} }

View file

@ -320,6 +320,7 @@ QVector<int> ChatListModel::ChatData::updateLastMessage(const QVariantMap &messa
changedRoles.append(RoleLastMessageDate); changedRoles.append(RoleLastMessageDate);
} }
if (prevSenderMessageText != senderMessageText()) { if (prevSenderMessageText != senderMessageText()) {
changedRoles.append(RoleFilter);
changedRoles.append(RoleLastMessageText); changedRoles.append(RoleLastMessageText);
} }
if (prevSenderMessageStatus != senderMessageStatus()) { if (prevSenderMessageStatus != senderMessageStatus()) {
@ -388,6 +389,9 @@ ChatListModel::ChatListModel(TDLibWrapper *tdLibWrapper, AppSettings *appSetting
relativeTimeRefreshTimer->setSingleShot(false); relativeTimeRefreshTimer->setSingleShot(false);
relativeTimeRefreshTimer->setInterval(30000); relativeTimeRefreshTimer->setInterval(30000);
connect(relativeTimeRefreshTimer, SIGNAL(timeout()), SLOT(handleRelativeTimeRefreshTimer())); connect(relativeTimeRefreshTimer, SIGNAL(timeout()), SLOT(handleRelativeTimeRefreshTimer()));
connect(this, SIGNAL(rowsInserted(QModelIndex,int,int)), SIGNAL(countChanged()));
connect(this, SIGNAL(rowsRemoved(QModelIndex,int,int)), SIGNAL(countChanged()));
connect(this, SIGNAL(modelReset()), SIGNAL(countChanged()));
} }
ChatListModel::~ChatListModel() ChatListModel::~ChatListModel()
@ -457,7 +461,7 @@ QVariant ChatListModel::data(const QModelIndex &index, int role) const
case ChatListModel::RoleIsChannel: return data->isChannel(); case ChatListModel::RoleIsChannel: return data->isChannel();
case ChatListModel::RoleIsMarkedAsUnread: return data->isMarkedAsUnread(); case ChatListModel::RoleIsMarkedAsUnread: return data->isMarkedAsUnread();
case ChatListModel::RoleIsPinned: return data->isPinned(); case ChatListModel::RoleIsPinned: return data->isPinned();
case ChatListModel::RoleFilter: return QString(data->title() + " " + data->senderMessageText()).trimmed(); case ChatListModel::RoleFilter: return data->title() + " " + data->senderMessageText();
case ChatListModel::RoleDraftMessageText: return data->draftMessageText(); case ChatListModel::RoleDraftMessageText: return data->draftMessageText();
case ChatListModel::RoleDraftMessageDate: return data->draftMessageDate(); case ChatListModel::RoleDraftMessageDate: return data->draftMessageDate();
} }
@ -891,6 +895,7 @@ void ChatListModel::handleChatTitleUpdated(const QString &chatId, const QString
chat->chatData.insert(TITLE, title); chat->chatData.insert(TITLE, title);
QVector<int> changedRoles; QVector<int> changedRoles;
changedRoles.append(ChatListModel::RoleTitle); changedRoles.append(ChatListModel::RoleTitle);
changedRoles.append(ChatListModel::RoleFilter);
const QModelIndex modelIndex(index(chatIndex)); const QModelIndex modelIndex(index(chatIndex));
emit dataChanged(modelIndex, modelIndex, changedRoles); emit dataChanged(modelIndex, modelIndex, changedRoles);
} else { } else {

View file

@ -28,6 +28,7 @@ class ChatListModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
Q_PROPERTY(bool showAllChats READ showAllChats WRITE setShowAllChats NOTIFY showAllChatsChanged) Q_PROPERTY(bool showAllChats READ showAllChats WRITE setShowAllChats NOTIFY showAllChatsChanged)
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public: public:
@ -57,9 +58,9 @@ public:
ChatListModel(TDLibWrapper *tdLibWrapper, AppSettings *appSettings); ChatListModel(TDLibWrapper *tdLibWrapper, AppSettings *appSettings);
~ChatListModel() override; ~ChatListModel() override;
virtual QHash<int,QByteArray> roleNames() const override; QHash<int,QByteArray> roleNames() const Q_DECL_OVERRIDE;
virtual int rowCount(const QModelIndex&) const override; int rowCount(const QModelIndex &index = QModelIndex()) const Q_DECL_OVERRIDE;
virtual QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const Q_DECL_OVERRIDE;
Q_INVOKABLE void redrawModel(); Q_INVOKABLE void redrawModel();
Q_INVOKABLE QVariantMap get(int row); Q_INVOKABLE QVariantMap get(int row);
@ -90,6 +91,7 @@ private slots:
void handleRelativeTimeRefreshTimer(); void handleRelativeTimeRefreshTimer();
signals: signals:
void countChanged();
void showAllChatsChanged(); void showAllChatsChanged();
void chatChanged(const qlonglong &changedChatId); void chatChanged(const qlonglong &changedChatId);
void chatJoined(const qlonglong &chatId, const QString &chatTitle); void chatJoined(const qlonglong &chatId, const QString &chatTitle);

View file

@ -42,6 +42,7 @@
#include "dbusadaptor.h" #include "dbusadaptor.h"
#include "processlauncher.h" #include "processlauncher.h"
#include "stickermanager.h" #include "stickermanager.h"
#include "textfiltermodel.h"
#include "tgsplugin.h" #include "tgsplugin.h"
#include "fernschreiberutils.h" #include "fernschreiberutils.h"
#include "knownusersmodel.h" #include "knownusersmodel.h"
@ -69,6 +70,7 @@ int main(int argc, char *argv[])
const char *uri = "WerkWolf.Fernschreiber"; const char *uri = "WerkWolf.Fernschreiber";
qmlRegisterType<TDLibFile>(uri, 1, 0, "TDLibFile"); qmlRegisterType<TDLibFile>(uri, 1, 0, "TDLibFile");
qmlRegisterType<NamedAction>(uri, 1, 0, "NamedAction"); qmlRegisterType<NamedAction>(uri, 1, 0, "NamedAction");
qmlRegisterType<TextFilterModel>(uri, 1, 0, "TextFilterModel");
qmlRegisterSingletonType<DebugLogJS>(uri, 1, 0, "DebugLog", DebugLogJS::createSingleton); qmlRegisterSingletonType<DebugLogJS>(uri, 1, 0, "DebugLog", DebugLogJS::createSingleton);
AppSettings *appSettings = new AppSettings(view.data()); AppSettings *appSettings = new AppSettings(view.data());
@ -89,11 +91,6 @@ int main(int argc, char *argv[])
ChatListModel chatListModel(tdLibWrapper, appSettings); ChatListModel chatListModel(tdLibWrapper, appSettings);
context->setContextProperty("chatListModel", &chatListModel); context->setContextProperty("chatListModel", &chatListModel);
QSortFilterProxyModel chatListProxyModel(view.data());
chatListProxyModel.setSourceModel(&chatListModel);
chatListProxyModel.setFilterRole(ChatListModel::RoleFilter);
chatListProxyModel.setFilterCaseSensitivity(Qt::CaseInsensitive);
context->setContextProperty("chatListProxyModel", &chatListProxyModel);
ChatModel chatModel(tdLibWrapper); ChatModel chatModel(tdLibWrapper);
context->setContextProperty("chatModel", &chatModel); context->setContextProperty("chatModel", &chatModel);

98
src/textfiltermodel.cpp Normal file
View file

@ -0,0 +1,98 @@
/*
This file is part of Fernschreiber.
Fernschreiber is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Fernschreiber is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/
#include "textfiltermodel.h"
#define DEBUG_MODULE TextFilterModel
#include "debuglog.h"
TextFilterModel::TextFilterModel(QObject *parent) : QSortFilterProxyModel(parent)
{
setDynamicSortFilter(true);
setFilterCaseSensitivity(Qt::CaseInsensitive);
setFilterFixedString(QString());
}
void TextFilterModel::setSource(QObject *model)
{
setSourceModel(qobject_cast<QAbstractItemModel*>(model));
}
void TextFilterModel::setSourceModel(QAbstractItemModel *model)
{
if (sourceModel() != model) {
LOG(model);
QSortFilterProxyModel::setSourceModel(model);
updateFilterRole();
emit sourceChanged();
}
}
QString TextFilterModel::getFilterRoleName() const
{
return filterRoleName;
}
void TextFilterModel::setFilterRoleName(QString role)
{
if (filterRoleName != role) {
filterRoleName = role;
LOG(role);
updateFilterRole();
emit filterRoleNameChanged();
}
}
QString TextFilterModel::getFilterText() const
{
return filterText;
}
void TextFilterModel::setFilterText(QString text)
{
if (filterText.compare(text, Qt::CaseInsensitive) != 0) {
filterText = text;
LOG(text);
setFilterFixedString(text);
emit filterTextChanged();
}
}
int TextFilterModel::findRole(QAbstractItemModel *model, QString role)
{
if (model && !role.isEmpty()) {
const QByteArray roleName(role.toUtf8());
const QHash<int,QByteArray> roleMap(model->roleNames());
const QList<int> roles(roleMap.keys());
const int n = roles.count();
for (int i = 0; i < n; i++) {
const QByteArray name(roleMap.value(roles.at(i)));
if (name == roleName) {
LOG(role << roles.at(i));
return roles.at(i);
}
}
LOG("Unknown role" << role);
}
return -1;
}
void TextFilterModel::updateFilterRole()
{
const int role = findRole(sourceModel(), filterRoleName);
setFilterRole((role >= 0) ? role : Qt::DisplayRole);
}

58
src/textfiltermodel.h Normal file
View file

@ -0,0 +1,58 @@
/*
This file is part of Fernschreiber.
Fernschreiber is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
Fernschreiber is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with Fernschreiber. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef TEXTFILTERMODEL_H
#define TEXTFILTERMODEL_H
#include <QSortFilterProxyModel>
class TextFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
Q_PROPERTY(QString filterRoleName READ getFilterRoleName WRITE setFilterRoleName NOTIFY filterRoleNameChanged)
Q_PROPERTY(QString filterText READ getFilterText WRITE setFilterText NOTIFY filterTextChanged)
Q_PROPERTY(QObject* sourceModel READ sourceModel WRITE setSource NOTIFY sourceChanged)
public:
TextFilterModel(QObject *parent = Q_NULLPTR);
void setSource(QObject* model);
void setSourceModel(QAbstractItemModel *model) Q_DECL_OVERRIDE;
QString getFilterRoleName() const;
void setFilterRoleName(QString role);
QString getFilterText() const;
void setFilterText(QString text);
signals:
void sourceChanged();
void filterRoleNameChanged();
void filterTextChanged();
private slots:
void updateFilterRole();
private:
static int findRole(QAbstractItemModel *model, QString role);
private:
QString filterRoleName;
QString filterText;
};
#endif // TEXTFILTERMODEL_H