The search for friendship

Add the ability to search in the user directory for friends.

This adds an option in roomlist when on the friends tab and opens a search dialog when clicked. The new search model searches the user directory for the given filter term.
This commit is contained in:
James Graham
2024-01-20 16:13:49 +00:00
committed by Tobias Fella
parent 4bd160cceb
commit 4b5d828bf8
13 changed files with 220 additions and 231 deletions

View File

@@ -169,7 +169,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/ManualRoomDialog.qml qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml qml/ExplorerDelegate.qml
qml/InviteUserPage.qml qml/InviteUserPage.qml
qml/StartChatPage.qml
qml/ImageEditorPage.qml qml/ImageEditorPage.qml
qml/WelcomePage.qml qml/WelcomePage.qml
qml/General.qml qml/General.qml
@@ -304,6 +303,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/TimelineEndDelegate.qml qml/TimelineEndDelegate.qml
qml/SearchPage.qml qml/SearchPage.qml
qml/ServerComboBox.qml qml/ServerComboBox.qml
qml/UserSearchPage.qml
RESOURCES RESOURCES
qml/confetti.png qml/confetti.png
qml/glowdot.png qml/glowdot.png

View File

@@ -26,7 +26,6 @@ void UserDirectoryListModel::setConnection(Connection *conn)
beginResetModel(); beginResetModel();
m_limited = false;
attempted = false; attempted = false;
users.clear(); users.clear();
@@ -37,53 +36,44 @@ void UserDirectoryListModel::setConnection(Connection *conn)
endResetModel(); endResetModel();
m_connection = conn; m_connection = conn;
if (job) {
job->abandon();
job = nullptr;
}
Q_EMIT connectionChanged(); Q_EMIT connectionChanged();
Q_EMIT limitedChanged();
if (m_job) {
m_job->abandon();
m_job = nullptr;
Q_EMIT searchingChanged();
}
} }
QString UserDirectoryListModel::keyword() const QString UserDirectoryListModel::searchText() const
{ {
return m_keyword; return m_searchText;
} }
void UserDirectoryListModel::setKeyword(const QString &value) void UserDirectoryListModel::setSearchText(const QString &value)
{ {
if (m_keyword == value) { if (m_searchText == value) {
return; return;
} }
m_keyword = value; m_searchText = value;
Q_EMIT searchTextChanged();
m_limited = false;
attempted = false; attempted = false;
if (job) {
job->abandon();
job = nullptr;
}
Q_EMIT keywordChanged();
Q_EMIT limitedChanged();
} }
bool UserDirectoryListModel::limited() const bool UserDirectoryListModel::searching() const
{ {
return m_limited; return m_job != nullptr;
} }
void UserDirectoryListModel::search(int count) void UserDirectoryListModel::search(int limit)
{ {
if (count < 1) { if (limit < 1) {
return; return;
} }
if (job) { if (m_job) {
qDebug() << "UserDirectoryListModel: Other jobs running, ignore"; qDebug() << "UserDirectoryListModel: Other jobs running, ignore";
return; return;
@@ -93,25 +83,22 @@ void UserDirectoryListModel::search(int count)
return; return;
} }
job = m_connection->callApi<SearchUserDirectoryJob>(m_keyword, count); m_job = m_connection->callApi<SearchUserDirectoryJob>(m_searchText, limit);
Q_EMIT searchingChanged();
connect(job, &BaseJob::finished, this, [this] { connect(m_job, &BaseJob::finished, this, [this] {
attempted = true; attempted = true;
if (job->status() == BaseJob::Success) { if (m_job->status() == BaseJob::Success) {
auto users = job->results(); auto users = m_job->results();
this->beginResetModel(); this->beginResetModel();
this->users = users; this->users = users;
this->m_limited = job->limited();
this->endResetModel(); this->endResetModel();
} }
this->job = nullptr; this->m_job = nullptr;
Q_EMIT searchingChanged();
Q_EMIT limitedChanged();
}); });
} }
@@ -127,7 +114,7 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
return {}; return {};
} }
auto user = users.at(index.row()); auto user = users.at(index.row());
if (role == NameRole) { if (role == DisplayNameRole) {
auto displayName = user.displayName; auto displayName = user.displayName;
if (!displayName.isEmpty()) { if (!displayName.isEmpty()) {
return displayName; return displayName;
@@ -142,18 +129,17 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
} }
if (role == AvatarRole) { if (role == AvatarRole) {
auto avatarUrl = user.avatarUrl; auto avatarUrl = user.avatarUrl;
if (avatarUrl.isEmpty() || !m_connection) {
if (avatarUrl.isEmpty()) { return QUrl();
return QString();
} }
return avatarUrl.url().remove(0, 6); return m_connection->makeMediaUrl(avatarUrl);
} }
if (role == UserIDRole) { if (role == UserIDRole) {
return user.userId; return user.userId;
} }
if (role == DirectChatsRole) { if (role == DirectChatExistsRole) {
if (!m_connection) { if (!m_connection) {
return QStringList(); return false;
}; };
auto userObj = m_connection->user(user.userId); auto userObj = m_connection->user(user.userId);
@@ -162,11 +148,11 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
if (userObj && directChats.contains(userObj)) { if (userObj && directChats.contains(userObj)) {
auto directChatsForUser = directChats.values(userObj); auto directChatsForUser = directChats.values(userObj);
if (!directChatsForUser.isEmpty()) { if (!directChatsForUser.isEmpty()) {
return QVariant::fromValue(directChatsForUser); return true;
} }
} }
return QStringList(); return false;
} }
return {}; return {};
@@ -176,10 +162,10 @@ QHash<int, QByteArray> UserDirectoryListModel::roleNames() const
{ {
QHash<int, QByteArray> roles; QHash<int, QByteArray> roles;
roles[NameRole] = "name"; roles[DisplayNameRole] = "displayName";
roles[AvatarRole] = "avatar"; roles[AvatarRole] = "avatarUrl";
roles[UserIDRole] = "userID"; roles[UserIDRole] = "userId";
roles[DirectChatsRole] = "directChats"; roles[DirectChatExistsRole] = "directChatExists";
return roles; return roles;
} }

View File

@@ -35,24 +35,24 @@ class UserDirectoryListModel : public QAbstractListModel
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged) Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/** /**
* @brief The keyword to use in the search. * @brief The text to search the public room list for.
*/ */
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged) Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
/** /**
* @brief Whether the current results have been truncated. * @brief Whether the model is searching.
*/ */
Q_PROPERTY(bool limited READ limited NOTIFY limitedChanged) Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
public: public:
/** /**
* @brief Defines the model roles. * @brief Defines the model roles.
*/ */
enum EventRoles { enum EventRoles {
NameRole = Qt::DisplayRole + 1, /**< The user's display name. */ DisplayNameRole = Qt::DisplayRole, /**< The user's display name. */
AvatarRole, /**< The source URL for the user's avatar. */ AvatarRole, /**< The source URL for the user's avatar. */
UserIDRole, /**< Matrix ID of the user. */ UserIDRole, /**< Matrix ID of the user. */
DirectChatsRole, /**< A list of direct chat matrix IDs with the user. */ DirectChatExistsRole, /**< Whether there is already a direct chat with the user. */
}; };
explicit UserDirectoryListModel(QObject *parent = nullptr); explicit UserDirectoryListModel(QObject *parent = nullptr);
@@ -60,17 +60,17 @@ public:
[[nodiscard]] Quotient::Connection *connection() const; [[nodiscard]] Quotient::Connection *connection() const;
void setConnection(Quotient::Connection *conn); void setConnection(Quotient::Connection *conn);
[[nodiscard]] QString keyword() const; [[nodiscard]] QString searchText() const;
void setKeyword(const QString &value); void setSearchText(const QString &searchText);
[[nodiscard]] bool limited() const; [[nodiscard]] bool searching() const;
/** /**
* @brief Get the given role value at the given index. * @brief Get the given role value at the given index.
* *
* @sa QAbstractItemModel::data * @sa QAbstractItemModel::data
*/ */
[[nodiscard]] QVariant data(const QModelIndex &index, int role = NameRole) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/** /**
* @brief Number of rows in the model. * @brief Number of rows in the model.
@@ -87,23 +87,23 @@ public:
[[nodiscard]] QHash<int, QByteArray> roleNames() const override; [[nodiscard]] QHash<int, QByteArray> roleNames() const override;
/** /**
* @brief Start the user search. * @brief Search the user directory.
*
* @param limit the maximum number of rooms to load.
*/ */
Q_INVOKABLE void search(int count = 50); Q_INVOKABLE void search(int limit = 50);
Q_SIGNALS: Q_SIGNALS:
void connectionChanged(); void connectionChanged();
void keywordChanged(); void searchTextChanged();
void limitedChanged(); void searchingChanged();
private: private:
Quotient::Connection *m_connection = nullptr; Quotient::Connection *m_connection = nullptr;
QString m_keyword; QString m_searchText;
bool m_limited = false;
bool attempted = false; bool attempted = false;
QList<Quotient::SearchUserDirectoryJob::User> users; QList<Quotient::SearchUserDirectoryJob::User> users;
Quotient::SearchUserDirectoryJob *job = nullptr; Quotient::SearchUserDirectoryJob *m_job = nullptr;
}; };

View File

@@ -281,6 +281,15 @@ bool NeoChatConnection::directChatExists(Quotient::User *user)
return directChats().contains(user); return directChats().contains(user);
} }
void NeoChatConnection::openOrCreateDirectChat(const QString &userId)
{
if (auto user = this->user(userId)) {
openOrCreateDirectChat(user);
} else {
qWarning() << "openOrCreateDirectChat: Couldn't get user object for ID " << userId << ", unable to open/request direct chat.";
}
}
void NeoChatConnection::openOrCreateDirectChat(User *user) void NeoChatConnection::openOrCreateDirectChat(User *user)
{ {
const auto existing = directChats(); const auto existing = directChats();

View File

@@ -95,7 +95,14 @@ public:
Q_INVOKABLE bool directChatExists(Quotient::User *user); Q_INVOKABLE bool directChatExists(Quotient::User *user);
/** /**
* @brief Join a direct chat with the given user. * @brief Join a direct chat with the given user ID.
*
* If a direct chat with the user doesn't exist one is created and then joined.
*/
Q_INVOKABLE void openOrCreateDirectChat(const QString &userId);
/**
* @brief Join a direct chat with the given user object.
* *
* If a direct chat with the user doesn't exist one is created and then joined. * If a direct chat with the user doesn't exist one is created and then joined.
*/ */

View File

@@ -32,9 +32,9 @@ RowLayout {
} }
} }
property Kirigami.Action chatAction: Kirigami.Action { property Kirigami.Action chatAction: Kirigami.Action {
text: i18n("Start a Chat") text: i18n("Find your friends")
icon.name: "list-add-user" icon.name: "list-add-user"
onTriggered: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection}, {title: i18nc("@title", "Start a Chat")}) onTriggered: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")})
} }
property Kirigami.Action roomAction: Kirigami.Action { property Kirigami.Action roomAction: Kirigami.Action {
text: i18n("Create a Room") text: i18n("Create a Room")

View File

@@ -64,10 +64,10 @@ ColumnLayout {
} }
}, },
Kirigami.Action { Kirigami.Action {
text: i18n("Start a Chat") text: i18n("Find your friends")
icon.name: "list-add-user" icon.name: "list-add-user"
onTriggered: { onTriggered: {
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection}, {title: i18nc("@title", "Start a Chat")}) pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")})
exploreTabBar.currentIndex = -1; exploreTabBar.currentIndex = -1;
} }
}, },

View File

@@ -90,7 +90,7 @@ SearchPage {
indeterminate: true indeterminate: true
} }
searchFieldPlaceholder: i18n("Find a room...") searchFieldPlaceholder: i18n("Find a room")
noResultPlaceholderMessage: i18nc("@info:label", "No public rooms found") noResultPlaceholderMessage: i18nc("@info:label", "No public rooms found")
Component { Component {

View File

@@ -45,13 +45,13 @@ Labs.MenuBar {
title: i18nc("menu", "File") title: i18nc("menu", "File")
Labs.MenuItem { Labs.MenuItem {
text: i18nc("menu", "New Private Chat…") text: i18nc("menu", "Find your friends")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && AccountRegistry.accountCount > 0 enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
onTriggered: pushReplaceLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection}) onTriggered: pushReplaceLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")})
} }
Labs.MenuItem { Labs.MenuItem {
text: i18nc("menu", "New Group…") text: i18nc("menu", "New Group…")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && AccountRegistry.accountCount > 0 enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
shortcut: StandardKey.New shortcut: StandardKey.New
onTriggered: { onTriggered: {
const dialog = createRoomDialog.createObject(root.overlay) const dialog = createRoomDialog.createObject(root.overlay)

View File

@@ -31,7 +31,7 @@ Kirigami.ScrollablePage {
Kirigami.SearchField { Kirigami.SearchField {
id: identifierField id: identifierField
property bool isUserID: text.match(/@(.+):(.+)/g) property bool isUserId: text.match(/@(.+):(.+)/g)
Layout.fillWidth: true Layout.fillWidth: true
placeholderText: i18n("Find a user...") placeholderText: i18n("Find a user...")
@@ -39,7 +39,7 @@ Kirigami.ScrollablePage {
} }
QQC2.Button { QQC2.Button {
visible: identifierField.isUserID visible: identifierField.isUserId
text: i18n("Add") text: i18n("Add")
highlighted: true highlighted: true
@@ -59,7 +59,7 @@ Kirigami.ScrollablePage {
id: userDictListModel id: userDictListModel
connection: root.room.connection connection: root.room.connection
keyword: identifierField.text searchText: identifierField.text
} }
Kirigami.PlaceholderMessage { Kirigami.PlaceholderMessage {
@@ -73,25 +73,25 @@ Kirigami.ScrollablePage {
delegate: Delegates.RoundedItemDelegate { delegate: Delegates.RoundedItemDelegate {
id: delegate id: delegate
required property string userID required property string userId
required property string name required property string displayName
required property string avatar required property url avatarUrl
property bool inRoom: room && room.containsUser(userID) property bool inRoom: room && room.containsUser(userId)
text: name text: displayName
contentItem: RowLayout { contentItem: RowLayout {
KirigamiComponents.Avatar { KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.medium Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: delegate.avatar ? ("image://mxc/" + delegate.avatar) : "" source: delegate.avatarUrl
name: delegate.name name: delegate.displayName
} }
Delegates.SubtitleContentItem { Delegates.SubtitleContentItem {
itemDelegate: delegate itemDelegate: delegate
subtitle: delegate.userID subtitle: delegate.userId
labelItem.textFormat: Text.PlainText labelItem.textFormat: Text.PlainText
} }
@@ -107,7 +107,7 @@ Kirigami.ScrollablePage {
if (inRoom) { if (inRoom) {
checked = true checked = true
} else { } else {
room.inviteToRoom(delegate.userID); room.inviteToRoom(delegate.userId);
applicationWindow().pageStack.layers.pop(); applicationWindow().pageStack.layers.pop();
} }
} }

View File

@@ -9,6 +9,7 @@ import QtQml.Models
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat import org.kde.neochat
import org.kde.neochat.config import org.kde.neochat.config
@@ -159,8 +160,15 @@ Kirigami.Page {
anchors.centerIn: parent anchors.centerIn: parent
width: parent.width - (Kirigami.Units.largeSpacing * 4) width: parent.width - (Kirigami.Units.largeSpacing * 4)
visible: listView.count == 0 visible: listView.count == 0
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("No rooms found") : i18n("Join some rooms to get started") text: if (sortFilterRoomListModel.filterText.length > 0) {
helpfulAction: Kirigami.Action { return spaceDrawer.showDirectChats ? i18n("No friends found") : i18n("No rooms found");
} else {
return spaceDrawer.showDirectChats ? i18n("You haven't added any of your friends yet, click below to search for them.") : i18n("Join some rooms to get started");
}
helpfulAction: spaceDrawer.showDirectChats ? userSearchAction : exploreRoomAction
Kirigami.Action {
id: exploreRoomAction
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add" icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms") text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
onTriggered: { onTriggered: {
@@ -179,6 +187,13 @@ Kirigami.Page {
}) })
} }
} }
Kirigami.Action {
id: userSearchAction
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in friend directory") : i18n("Find your friends")
onTriggered: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")})
}
} }
ItemSelectionModel { ItemSelectionModel {
@@ -286,6 +301,16 @@ Kirigami.Page {
Keys.onReturnPressed: RoomManager.enterRoom(currentRoom) Keys.onReturnPressed: RoomManager.enterRoom(currentRoom)
} }
} }
footer: Delegates.RoundedItemDelegate {
visible: listView.view.count > 0 && spaceDrawer.showDirectChats
text: i18n("Find your friends")
icon.name: "list-add-user"
icon.width: Kirigami.Units.gridUnit * 2
icon.height: Kirigami.Units.gridUnit * 2
onClicked: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")})
}
} }
} }
} }

View File

@@ -1,138 +0,0 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.neochat
Kirigami.ScrollablePage {
id: root
property NeoChatConnection connection
title: i18n("Start a Chat")
header: QQC2.Control {
padding: Kirigami.Units.largeSpacing
contentItem: RowLayout {
Kirigami.SearchField {
id: identifierField
property bool isUserID: text.match(/@(.+):(.+)/g)
Layout.fillWidth: true
placeholderText: i18n("Find a user...")
onAccepted: userDictListModel.search()
}
QQC2.Button {
visible: identifierField.isUserID
text: i18n("Chat")
highlighted: true
onClicked: {
connection.requestDirectChat(identifierField.text);
applicationWindow().pageStack.layers.pop();
}
}
}
}
ListView {
id: userDictListView
clip: true
spacing: Kirigami.Units.smallSpacing
model: UserDirectoryListModel {
id: userDictListModel
connection: root.connection
keyword: identifierField.text
}
delegate: Delegates.RoundedItemDelegate {
id: delegate
required property string userID
required property string avatar
required property string name
required property var directChats
text: name
contentItem: RowLayout {
KirigamiComponents.Avatar {
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
source: delegate.avatar ? ("image://mxc/" + delegate.avatar) : ""
name: delegate.name
}
Delegates.SubtitleContentItem {
itemDelegate: delegate
subtitle: delegate.userID
Layout.fillWidth: true
labelItem.textFormat: Text.PlainText
}
QQC2.Button {
id: joinChatButton
visible: delegate.directChats && delegate.directChats.length > 0
text: i18n("Join existing chat")
display: QQC2.Button.IconOnly
icon.name: "document-send"
onClicked: {
connection.requestDirectChat(delegate.userID);
applicationWindow().pageStack.layers.pop();
}
Layout.alignment: Qt.AlignRight
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
QQC2.Button {
icon.name: "irc-join-channel"
// We wants to make sure an user can't start more than one
// chat with someone.
visible: !joinChatButton.visible
text: i18n("Create new chat")
display: QQC2.Button.IconOnly
onClicked: {
connection.requestDirectChat(delegate.userID);
applicationWindow().pageStack.layers.pop();
}
Layout.alignment: Qt.AlignRight
QQC2.ToolTip.text: text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
}
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
visible: userDictListView.count < 1
text: i18n("No users available")
}
}
}

100
src/qml/UserSearchPage.qml Normal file
View File

@@ -0,0 +1,100 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import Qt.labs.qmlmodels
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.kirigamiaddons.labs.components as Components
import org.kde.neochat
/**
* @brief Component for finding rooms for the public list.
*
* This component is based on a SearchPage, adding the functionality to select or
* enter a server in the header, as well as the ability to manually type a room in
* if the public room search cannot find it.
*
* @sa SearchPage
*/
SearchPage {
id: root
/**
* @brief The connection for the current local user.
*/
required property NeoChatConnection connection
title: i18nc("@action:title", "Find Your Friends")
Component.onCompleted: focusSearch()
model: UserDirectoryListModel {
id: userSearchModel
connection: root.connection
}
modelDelegate: Delegates.RoundedItemDelegate {
id: userDelegate
required property string userId
required property string displayName
required property url avatarUrl
required property var directChatExists
text: displayName
onClicked: {
root.connection.openOrCreateDirectChat(userDelegate.userId)
root.closeDialog()
}
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
Components.Avatar {
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
Layout.alignment: Qt.AlignTop
source: userDelegate.avatarUrl
name: userDelegate.displayName
}
Delegates.SubtitleContentItem {
itemDelegate: userDelegate
subtitle: userDelegate.userId
labelItem.textFormat: Text.PlainText
}
QQC2.Label {
visible: userDelegate.directChatExists
text: i18n("Friends")
textFormat: Text.PlainText
color: Kirigami.Theme.positiveTextColor
}
}
}
searchFieldPlaceholder: i18n("Find your friends…")
noSearchPlaceholderMessage: i18n("Enter text to start searching for your friends")
noResultPlaceholderMessage: i18nc("@info:label", "No matches found")
Component {
id: manualRoomDialog
ManualRoomDialog {}
}
QtObject {
id: _private
function openManualRoomDialog() {
let dialog = manualRoomDialog.createObject(applicationWindow().overlay, {connection: root.connection});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
root.closeDialog();
});
dialog.open();
}
}
}