From 4b5d828bf826cf7b54ef53184de4d20fae9748aa Mon Sep 17 00:00:00 2001 From: James Graham Date: Sat, 20 Jan 2024 16:13:49 +0000 Subject: [PATCH] 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. --- src/CMakeLists.txt | 2 +- src/models/userdirectorylistmodel.cpp | 86 +++++++--------- src/models/userdirectorylistmodel.h | 36 +++---- src/neochatconnection.cpp | 9 ++ src/neochatconnection.h | 9 +- src/qml/ExploreComponent.qml | 4 +- src/qml/ExploreComponentMobile.qml | 4 +- src/qml/ExploreRoomsPage.qml | 2 +- src/qml/GlobalMenu.qml | 8 +- src/qml/InviteUserPage.qml | 24 ++--- src/qml/RoomListPage.qml | 29 +++++- src/qml/StartChatPage.qml | 138 -------------------------- src/qml/UserSearchPage.qml | 100 +++++++++++++++++++ 13 files changed, 220 insertions(+), 231 deletions(-) delete mode 100644 src/qml/StartChatPage.qml create mode 100644 src/qml/UserSearchPage.qml diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index b0a8ce63e..a5cb7d534 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -169,7 +169,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN qml/ManualRoomDialog.qml qml/ExplorerDelegate.qml qml/InviteUserPage.qml - qml/StartChatPage.qml qml/ImageEditorPage.qml qml/WelcomePage.qml qml/General.qml @@ -304,6 +303,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN qml/TimelineEndDelegate.qml qml/SearchPage.qml qml/ServerComboBox.qml + qml/UserSearchPage.qml RESOURCES qml/confetti.png qml/glowdot.png diff --git a/src/models/userdirectorylistmodel.cpp b/src/models/userdirectorylistmodel.cpp index 1f28c2651..6226a6492 100644 --- a/src/models/userdirectorylistmodel.cpp +++ b/src/models/userdirectorylistmodel.cpp @@ -26,7 +26,6 @@ void UserDirectoryListModel::setConnection(Connection *conn) beginResetModel(); - m_limited = false; attempted = false; users.clear(); @@ -37,53 +36,44 @@ void UserDirectoryListModel::setConnection(Connection *conn) endResetModel(); m_connection = conn; - - if (job) { - job->abandon(); - job = nullptr; - } - 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; } - m_keyword = value; + m_searchText = value; + Q_EMIT searchTextChanged(); - m_limited = 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; } - if (job) { + if (m_job) { qDebug() << "UserDirectoryListModel: Other jobs running, ignore"; return; @@ -93,25 +83,22 @@ void UserDirectoryListModel::search(int count) return; } - job = m_connection->callApi(m_keyword, count); + m_job = m_connection->callApi(m_searchText, limit); + Q_EMIT searchingChanged(); - connect(job, &BaseJob::finished, this, [this] { + connect(m_job, &BaseJob::finished, this, [this] { attempted = true; - if (job->status() == BaseJob::Success) { - auto users = job->results(); + if (m_job->status() == BaseJob::Success) { + auto users = m_job->results(); this->beginResetModel(); - this->users = users; - this->m_limited = job->limited(); - this->endResetModel(); } - this->job = nullptr; - - Q_EMIT limitedChanged(); + this->m_job = nullptr; + Q_EMIT searchingChanged(); }); } @@ -127,7 +114,7 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const return {}; } auto user = users.at(index.row()); - if (role == NameRole) { + if (role == DisplayNameRole) { auto displayName = user.displayName; if (!displayName.isEmpty()) { return displayName; @@ -142,18 +129,17 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const } if (role == AvatarRole) { auto avatarUrl = user.avatarUrl; - - if (avatarUrl.isEmpty()) { - return QString(); + if (avatarUrl.isEmpty() || !m_connection) { + return QUrl(); } - return avatarUrl.url().remove(0, 6); + return m_connection->makeMediaUrl(avatarUrl); } if (role == UserIDRole) { return user.userId; } - if (role == DirectChatsRole) { + if (role == DirectChatExistsRole) { if (!m_connection) { - return QStringList(); + return false; }; 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)) { auto directChatsForUser = directChats.values(userObj); if (!directChatsForUser.isEmpty()) { - return QVariant::fromValue(directChatsForUser); + return true; } } - return QStringList(); + return false; } return {}; @@ -176,10 +162,10 @@ QHash UserDirectoryListModel::roleNames() const { QHash roles; - roles[NameRole] = "name"; - roles[AvatarRole] = "avatar"; - roles[UserIDRole] = "userID"; - roles[DirectChatsRole] = "directChats"; + roles[DisplayNameRole] = "displayName"; + roles[AvatarRole] = "avatarUrl"; + roles[UserIDRole] = "userId"; + roles[DirectChatExistsRole] = "directChatExists"; return roles; } diff --git a/src/models/userdirectorylistmodel.h b/src/models/userdirectorylistmodel.h index 2fb88d2c8..76b1872d6 100644 --- a/src/models/userdirectorylistmodel.h +++ b/src/models/userdirectorylistmodel.h @@ -35,24 +35,24 @@ class UserDirectoryListModel : public QAbstractListModel 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: /** * @brief Defines the model roles. */ 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. */ 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); @@ -60,17 +60,17 @@ public: [[nodiscard]] Quotient::Connection *connection() const; void setConnection(Quotient::Connection *conn); - [[nodiscard]] QString keyword() const; - void setKeyword(const QString &value); + [[nodiscard]] QString searchText() const; + void setSearchText(const QString &searchText); - [[nodiscard]] bool limited() const; + [[nodiscard]] bool searching() const; /** * @brief Get the given role value at the given index. * * @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. @@ -87,23 +87,23 @@ public: [[nodiscard]] QHash 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: void connectionChanged(); - void keywordChanged(); - void limitedChanged(); + void searchTextChanged(); + void searchingChanged(); private: Quotient::Connection *m_connection = nullptr; - QString m_keyword; - bool m_limited = false; + QString m_searchText; bool attempted = false; - QList users; - Quotient::SearchUserDirectoryJob *job = nullptr; + Quotient::SearchUserDirectoryJob *m_job = nullptr; }; diff --git a/src/neochatconnection.cpp b/src/neochatconnection.cpp index 8f9be784c..5da6eba60 100644 --- a/src/neochatconnection.cpp +++ b/src/neochatconnection.cpp @@ -281,6 +281,15 @@ bool NeoChatConnection::directChatExists(Quotient::User *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) { const auto existing = directChats(); diff --git a/src/neochatconnection.h b/src/neochatconnection.h index 2857aa76f..c4e05cf6d 100644 --- a/src/neochatconnection.h +++ b/src/neochatconnection.h @@ -95,7 +95,14 @@ public: 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. */ diff --git a/src/qml/ExploreComponent.qml b/src/qml/ExploreComponent.qml index d66808683..9eae69887 100644 --- a/src/qml/ExploreComponent.qml +++ b/src/qml/ExploreComponent.qml @@ -32,9 +32,9 @@ RowLayout { } } property Kirigami.Action chatAction: Kirigami.Action { - text: i18n("Start a Chat") + text: i18n("Find your friends") 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 { text: i18n("Create a Room") diff --git a/src/qml/ExploreComponentMobile.qml b/src/qml/ExploreComponentMobile.qml index ddb29128a..507face73 100644 --- a/src/qml/ExploreComponentMobile.qml +++ b/src/qml/ExploreComponentMobile.qml @@ -64,10 +64,10 @@ ColumnLayout { } }, Kirigami.Action { - text: i18n("Start a Chat") + text: i18n("Find your friends") icon.name: "list-add-user" 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; } }, diff --git a/src/qml/ExploreRoomsPage.qml b/src/qml/ExploreRoomsPage.qml index 54657ce1b..4ea8efbd3 100644 --- a/src/qml/ExploreRoomsPage.qml +++ b/src/qml/ExploreRoomsPage.qml @@ -90,7 +90,7 @@ SearchPage { indeterminate: true } - searchFieldPlaceholder: i18n("Find a room...") + searchFieldPlaceholder: i18n("Find a room…") noResultPlaceholderMessage: i18nc("@info:label", "No public rooms found") Component { diff --git a/src/qml/GlobalMenu.qml b/src/qml/GlobalMenu.qml index 0aa8b7657..b796632b6 100644 --- a/src/qml/GlobalMenu.qml +++ b/src/qml/GlobalMenu.qml @@ -45,13 +45,13 @@ Labs.MenuBar { title: i18nc("menu", "File") Labs.MenuItem { - text: i18nc("menu", "New Private Chat…") - enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && AccountRegistry.accountCount > 0 - onTriggered: pushReplaceLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection}) + text: i18nc("menu", "Find your friends") + enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0 + onTriggered: pushReplaceLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")}) } Labs.MenuItem { 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 onTriggered: { const dialog = createRoomDialog.createObject(root.overlay) diff --git a/src/qml/InviteUserPage.qml b/src/qml/InviteUserPage.qml index b9c42835e..c87e033fc 100644 --- a/src/qml/InviteUserPage.qml +++ b/src/qml/InviteUserPage.qml @@ -31,7 +31,7 @@ Kirigami.ScrollablePage { Kirigami.SearchField { id: identifierField - property bool isUserID: text.match(/@(.+):(.+)/g) + property bool isUserId: text.match(/@(.+):(.+)/g) Layout.fillWidth: true placeholderText: i18n("Find a user...") @@ -39,7 +39,7 @@ Kirigami.ScrollablePage { } QQC2.Button { - visible: identifierField.isUserID + visible: identifierField.isUserId text: i18n("Add") highlighted: true @@ -59,7 +59,7 @@ Kirigami.ScrollablePage { id: userDictListModel connection: root.room.connection - keyword: identifierField.text + searchText: identifierField.text } Kirigami.PlaceholderMessage { @@ -73,25 +73,25 @@ Kirigami.ScrollablePage { delegate: Delegates.RoundedItemDelegate { id: delegate - required property string userID - required property string name - required property string avatar + required property string userId + required property string displayName + required property url avatarUrl - property bool inRoom: room && room.containsUser(userID) + property bool inRoom: room && room.containsUser(userId) - text: name + text: displayName 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 + source: delegate.avatarUrl + name: delegate.displayName } Delegates.SubtitleContentItem { itemDelegate: delegate - subtitle: delegate.userID + subtitle: delegate.userId labelItem.textFormat: Text.PlainText } @@ -107,7 +107,7 @@ Kirigami.ScrollablePage { if (inRoom) { checked = true } else { - room.inviteToRoom(delegate.userID); + room.inviteToRoom(delegate.userId); applicationWindow().pageStack.layers.pop(); } } diff --git a/src/qml/RoomListPage.qml b/src/qml/RoomListPage.qml index cd8e9088f..7c40ceaa7 100644 --- a/src/qml/RoomListPage.qml +++ b/src/qml/RoomListPage.qml @@ -9,6 +9,7 @@ import QtQml.Models import org.kde.kirigami as Kirigami import org.kde.kirigamiaddons.components as KirigamiComponents +import org.kde.kirigamiaddons.delegates as Delegates import org.kde.neochat import org.kde.neochat.config @@ -159,8 +160,15 @@ Kirigami.Page { anchors.centerIn: parent width: parent.width - (Kirigami.Units.largeSpacing * 4) visible: listView.count == 0 - text: sortFilterRoomListModel.filterText.length > 0 ? i18n("No rooms found") : i18n("Join some rooms to get started") - helpfulAction: Kirigami.Action { + text: if (sortFilterRoomListModel.filterText.length > 0) { + 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" text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms") 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 { @@ -286,6 +301,16 @@ Kirigami.Page { 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")}) + } } } } diff --git a/src/qml/StartChatPage.qml b/src/qml/StartChatPage.qml deleted file mode 100644 index f09afecfa..000000000 --- a/src/qml/StartChatPage.qml +++ /dev/null @@ -1,138 +0,0 @@ -// SPDX-FileCopyrightText: 2019 Black Hat -// SPDX-FileCopyrightText: 2020 Carl Schwan -// 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") - } - } -} diff --git a/src/qml/UserSearchPage.qml b/src/qml/UserSearchPage.qml new file mode 100644 index 000000000..93affd3d4 --- /dev/null +++ b/src/qml/UserSearchPage.qml @@ -0,0 +1,100 @@ +// SPDX-FileCopyrightText: 2024 James Graham +// 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(); + } + } +}