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:
committed by
Tobias Fella
parent
4bd160cceb
commit
4b5d828bf8
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -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")
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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")})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
100
src/qml/UserSearchPage.qml
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user