Implement searching in rooms

BUG: 457839
This commit is contained in:
Tobias Fella
2022-11-15 23:12:39 +01:00
parent 7f27056a34
commit 51e0023384
8 changed files with 361 additions and 3 deletions

View File

@@ -45,6 +45,7 @@ add_library(neochat STATIC
serverlistmodel.cpp
statemodel.cpp
filetransferpseudojob.cpp
searchmodel.cpp
)
add_executable(neochat-app

View File

@@ -59,6 +59,7 @@
#include "neochatroom.h"
#include "neochatuser.h"
#include "notificationsmanager.h"
#include "searchmodel.h"
#ifdef QUOTIENT_07
#include "pollhandler.h"
#endif
@@ -228,6 +229,7 @@ int main(int argc, char *argv[])
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
#ifdef QUOTIENT_07
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
#endif

View File

@@ -20,7 +20,7 @@ ColumnLayout {
default property alias innerObject : column.children
property Item hoverComponent: hoverActions
property Item hoverComponent: hoverActions ?? null
property bool isEmote: false
property bool cardBackground: true
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
@@ -106,6 +106,9 @@ ColumnLayout {
// Show hover actions by updating the global hover component to this delegate
function updateHoverComponent() {
if (!hoverComponent) {
return;
}
if (hovered && !Kirigami.Settings.isMobile) {
hoverComponent.delegate = root
hoverComponent.bubble = bubble
@@ -229,10 +232,10 @@ ColumnLayout {
QQC2.Label {
id: timeLabel
text: visible ? time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
text: visible ? model.time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
color: Kirigami.Theme.disabledTextColor
QQC2.ToolTip.visible: hoverHandler.hovered
QQC2.ToolTip.text: time.toLocaleString(Qt.locale(), Locale.LongFormat)
QQC2.ToolTip.text: model.time.toLocaleString(Qt.locale(), Locale.LongFormat)
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
HoverHandler {
@@ -268,6 +271,7 @@ ColumnLayout {
id: bubbleBackground
visible: cardBackground && !Config.compactLayout
anchors.fill: parent
Kirigami.Theme.colorSet: Kirigami.Theme.View
color: {
if (model.author.isLocalUser) {
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)

View File

@@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import org.kde.kirigami 2.20 as Kirigami
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
id: searchPage
property var currentRoom
title: i18nc("@action:title", "Search Messages")
Kirigami.Theme.colorSet: Kirigami.Theme.Window
SearchModel {
id: searchModel
connection: Controller.activeConnection
searchText: searchField.text
room: searchPage.currentRoom
}
header: RowLayout {
Kirigami.SearchField {
id: searchField
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.leftMargin: Kirigami.Units.smallSpacing
Layout.fillWidth: true
Keys.onEnterPressed: searchButton.clicked()
Keys.onReturnPressed: searchButton.clicked()
}
QQC2.Button {
id: searchButton
Layout.topMargin: Kirigami.Units.smallSpacing
Layout.rightMargin: Kirigami.Units.smallSpacing
onClicked: searchModel.search()
icon.name: "search"
}
}
ListView {
id: messageListView
Layout.fillWidth: true
Layout.fillHeight: true
spacing: 0
verticalLayoutDirection: ListView.BottomToTop
section.property: "section"
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
visible: searchField.text.length === 0 && messageListView.count === 0
text: i18n("Enter a text to start searching")
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
visible: searchField.text.length > 0 && messageListView.count === 0 && !searchModel.searching
text: i18n("No results found")
}
Kirigami.LoadingPlaceholder {
anchors.centerIn: parent
visible: searchModel.searching
}
model: searchModel
delegate: EventDelegate {}
}
}

View File

@@ -111,6 +111,23 @@ Kirigami.OverlayDrawer {
text: devtoolsButton.text
}
}
QQC2.ToolButton {
id: searchButton
Layout.alignment: Qt.AlignRight
icon.name: "search"
text: i18n("Search in this room")
display: QQC2.AbstractButton.IconOnly
visible: Controller.quotientMinorVersion > 6
onClicked: {
pageStack.pushDialogLayer("qrc:/SearchPage.qml", {
currentRoom: room
}, {
title: i18nc("@action:title", "Search")
})
}
}
QQC2.ToolButton {
id: inviteButton

View File

@@ -96,5 +96,6 @@
<file alias="EmojiTonesPicker.qml">qml/Component/Emoji/EmojiTonesPicker.qml</file>
<file alias="EmojiDelegate.qml">qml/Component/Emoji/EmojiDelegate.qml</file>
<file alias="EmojiGrid.qml">qml/Component/Emoji/EmojiGrid.qml</file>
<file alias="SearchPage.qml">qml/Page/SearchPage.qml</file>
</qresource>
</RCC>

180
src/searchmodel.cpp Normal file
View File

@@ -0,0 +1,180 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "searchmodel.h"
#include "messageeventmodel.h"
#include "neochatroom.h"
#include "neochatuser.h"
#include <KLocalizedString>
#include <connection.h>
#ifdef QUOTIENT_07
#include <csapi/search.h>
#endif
using namespace Quotient;
// TODO search only in the current room
SearchModel::SearchModel(QObject *parent)
: QAbstractListModel(parent)
{
}
QString SearchModel::searchText() const
{
return m_searchText;
}
void SearchModel::setSearchText(const QString &searchText)
{
m_searchText = searchText;
Q_EMIT searchTextChanged();
}
void SearchModel::search()
{
#ifdef QUOTIENT_07
Q_ASSERT(m_connection);
setSearching(true);
if (m_job) {
m_job->abandon();
m_job = nullptr;
}
SearchJob::RoomEventsCriteria criteria{
m_searchText,
{},
RoomEventFilter{
.rooms = {m_room->id()},
},
"recent",
SearchJob::IncludeEventContext{3, 3, true},
false,
none,
};
auto job = m_connection->callApi<SearchJob>(SearchJob::Categories{criteria});
m_job = job;
connect(job, &BaseJob::finished, this, [=] {
beginResetModel();
m_result = job->searchCategories().roomEvents;
endResetModel();
setSearching(false);
m_job = nullptr;
// TODO error handling
});
#endif
}
Connection *SearchModel::connection() const
{
return m_connection;
}
void SearchModel::setConnection(Connection *connection)
{
m_connection = connection;
Q_EMIT connectionChanged();
}
QVariant SearchModel::data(const QModelIndex &index, int role) const
{
#ifdef QUOTIENT_07
auto row = index.row();
const auto &event = *m_result->results[row].result;
switch (role) {
case DisplayRole:
return m_room->eventToString(*m_result->results[row].result);
case ShowAuthorRole:
return true;
case AuthorRole:
return QVariantMap{
{"isLocalUser", event.senderId() == m_room->localUser()->id()},
{"id", event.senderId()},
{"avatarMediaId", m_connection->user(event.senderId())->avatarMediaId(m_room)},
{"avatarUrl", m_connection->user(event.senderId())->avatarUrl(m_room)},
{"displayName", m_connection->user(event.senderId())->displayname(m_room)},
{"display", m_connection->user(event.senderId())->name()},
{"color", dynamic_cast<NeoChatUser *>(m_connection->user(event.senderId()))->color()},
{"object", QVariant::fromValue(m_connection->user(event.senderId()))},
};
case ShowSectionRole:
if (row == 0) {
return true;
}
return event.originTimestamp().date() != m_result->results[row - 1].result->originTimestamp().date();
case SectionRole:
return renderDate(event.originTimestamp());
case TimeRole:
return event.originTimestamp();
}
return MessageEventModel::DelegateType::Message;
#endif
return {};
}
int SearchModel::rowCount(const QModelIndex &parent) const
{
#ifdef QUOTIENT_07
if (m_result.has_value()) {
return m_result->results.size();
}
#endif
return 0;
}
QHash<int, QByteArray> SearchModel::roleNames() const
{
return {
{EventTypeRole, "eventType"},
{DisplayRole, "display"},
{AuthorRole, "author"},
{ShowSectionRole, "showSection"},
{SectionRole, "section"},
{TimeRole, "time"},
{ShowAuthorRole, "showAuthor"},
};
}
NeoChatRoom *SearchModel::room() const
{
return m_room;
}
void SearchModel::setRoom(NeoChatRoom *room)
{
m_room = room;
Q_EMIT roomChanged();
}
// TODO deduplicate with messageeventmodel
QString renderDate(const QDateTime &timestamp)
{
auto date = timestamp.toLocalTime().date();
if (date == QDate::currentDate()) {
return i18n("Today");
}
if (date == QDate::currentDate().addDays(-1)) {
return i18n("Yesterday");
}
if (date == QDate::currentDate().addDays(-2)) {
return i18n("The day before yesterday");
}
if (date > QDate::currentDate().addDays(-7)) {
return date.toString("dddd");
}
return QLocale::system().toString(date, QLocale::ShortFormat);
}
bool SearchModel::searching() const
{
return m_searching;
}
void SearchModel::setSearching(bool searching)
{
m_searching = searching;
Q_EMIT searchingChanged();
}

78
src/searchmodel.h Normal file
View File

@@ -0,0 +1,78 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QAbstractListModel>
#include <QString>
#ifdef QUOTIENT_07
#include <csapi/search.h>
#endif
namespace Quotient
{
class Connection;
}
class NeoChatRoom;
class SearchModel : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
public:
enum Roles {
DisplayRole = Qt::DisplayRole,
EventTypeRole,
ShowAuthorRole,
AuthorRole,
ShowSectionRole,
SectionRole,
TimeRole,
};
Q_ENUM(Roles);
SearchModel(QObject *parent = nullptr);
QString searchText() const;
void setSearchText(const QString &searchText);
Quotient::Connection *connection() const;
void setConnection(Quotient::Connection *connection);
NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
Q_INVOKABLE void search();
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QHash<int, QByteArray> roleNames() const override;
bool searching() const;
Q_SIGNALS:
void searchTextChanged();
void connectionChanged();
void roomChanged();
void searchingChanged();
private:
void setSearching(bool searching);
QString m_searchText;
Quotient::Connection *m_connection = nullptr;
NeoChatRoom *m_room = nullptr;
#ifdef QUOTIENT_07
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
Quotient::SearchJob *m_job = nullptr;
#endif
bool m_searching = false;
};
QString renderDate(const QDateTime &dateTime);