diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp
index 52dd4e43..cf78d63e 100644
--- a/src/InviteesModel.cpp
+++ b/src/InviteesModel.cpp
@@ -17,7 +17,7 @@ InviteesModel::InviteesModel(QObject *parent)
}
void
-InviteesModel::addUser(QString mxid)
+InviteesModel::addUser(QString mxid, QString displayName, QString avatarUrl)
{
for (const auto &invitee : qAsConst(invitees_))
if (invitee->mxid_ == mxid)
@@ -25,7 +25,7 @@ InviteesModel::addUser(QString mxid)
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
- auto invitee = new Invitee{mxid, this};
+ auto invitee = new Invitee{mxid, displayName, avatarUrl, this};
auto indexOfInvitee = invitees_.count();
connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
@@ -85,21 +85,30 @@ InviteesModel::mxids()
return mxidList;
}
-Invitee::Invitee(QString mxid, QObject *parent)
+Invitee::Invitee(QString mxid, QString displayName, QString avatarUrl, QObject *parent)
: QObject{parent}
, mxid_{std::move(mxid)}
{
- http::client()->get_profile(
- mxid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
- if (err) {
- nhlog::net()->warn("failed to retrieve profile info");
- emit userInfoLoaded();
- return;
- }
+ // checking for empty avatarUrl will cause profiles that don't have an avatar
+ // to needlessly be loaded. Can we make sure we either provide both or none?
+ if (displayName == "" && avatarUrl == "") {
+ http::client()->get_profile(
+ mxid_.toStdString(),
+ [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->warn("failed to retrieve profile info");
+ emit userInfoLoaded();
+ return;
+ }
- displayName_ = QString::fromStdString(res.display_name);
- avatarUrl_ = QString::fromStdString(res.avatar_url);
+ displayName_ = QString::fromStdString(res.display_name);
+ avatarUrl_ = QString::fromStdString(res.avatar_url);
- emit userInfoLoaded();
- });
+ emit userInfoLoaded();
+ });
+ } else {
+ displayName_ = displayName;
+ avatarUrl_ = avatarUrl;
+ emit userInfoLoaded();
+ }
}
diff --git a/src/InviteesModel.h b/src/InviteesModel.h
index 91b89a21..828f80e2 100644
--- a/src/InviteesModel.h
+++ b/src/InviteesModel.h
@@ -15,7 +15,10 @@ class Invitee final : public QObject
Q_OBJECT
public:
- Invitee(QString mxid, QObject *parent = nullptr);
+ Invitee(QString mxid,
+ QString displayName = "",
+ QString avatarUrl = "",
+ QObject *parent = nullptr);
signals:
void userInfoLoaded();
@@ -44,7 +47,7 @@ public:
InviteesModel(QObject *parent = nullptr);
- Q_INVOKABLE void addUser(QString mxid);
+ Q_INVOKABLE void addUser(QString mxid, QString displayName = "", QString avatarUrl = "");
Q_INVOKABLE void removeUser(QString mxid);
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index 8c2b4c35..8b453346 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -38,6 +38,7 @@
#include "RoomsModel.h"
#include "SingleImagePackModel.h"
#include "TrayIcon.h"
+#include "UserDirectoryModel.h"
#include "UserSettingsPage.h"
#include "UsersModel.h"
#include "Utils.h"
@@ -70,6 +71,7 @@ Q_DECLARE_METATYPE(std::vector<DeviceInfo>)
Q_DECLARE_METATYPE(std::vector<mtx::responses::PublicRoomsChunk>)
Q_DECLARE_METATYPE(mtx::responses::PublicRoom)
Q_DECLARE_METATYPE(mtx::responses::Profile)
+Q_DECLARE_METATYPE(mtx::responses::User)
MainWindow *MainWindow::instance_ = nullptr;
@@ -148,6 +150,7 @@ MainWindow::registerQmlTypes()
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
qRegisterMetaType<mtx::responses::PublicRoom>();
+ qRegisterMetaType<mtx::responses::User>();
qRegisterMetaType<mtx::responses::Profile>();
qRegisterMetaType<CombinedImagePackModel *>();
qRegisterMetaType<RoomSettingsAllowedRoomsModel *>();
@@ -155,7 +158,9 @@ MainWindow::registerQmlTypes()
qRegisterMetaType<std::vector<DeviceInfo>>();
qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
+ qRegisterMetaType<std::vector<mtx::responses::User>>();
+ qRegisterMetaType<mtx::responses::User>();
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
"im.nheko",
1,
@@ -185,6 +190,7 @@ MainWindow::registerQmlTypes()
qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
+ qmlRegisterType<UserDirectoryModel>("im.nheko", 1, 0, "UserDirectoryModel");
qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login");
qmlRegisterType<RegisterPage>("im.nheko", 1, 0, "Registration");
qmlRegisterType<HiddenEvents>("im.nheko", 1, 0, "HiddenEvents");
diff --git a/src/UserDirectoryModel.cpp b/src/UserDirectoryModel.cpp
new file mode 100644
index 00000000..2c44df40
--- /dev/null
+++ b/src/UserDirectoryModel.cpp
@@ -0,0 +1,105 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+// SPDX-FileCopyrightText: 2023 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "UserDirectoryModel.h"
+
+#include "Cache.h"
+#include "Logging.h"
+#include <QSharedPointer>
+#include "MatrixClient.h"
+#include "mtx/responses/users.hpp"
+
+UserDirectoryModel::UserDirectoryModel(QObject *parent)
+ : QAbstractListModel{parent}
+{
+}
+
+QHash<int, QByteArray>
+UserDirectoryModel::roleNames() const
+{
+ return {
+ {Roles::DisplayName, "displayName"},
+ {Roles::Mxid, "userid"},
+ {Roles::AvatarUrl, "avatarUrl"},
+ };
+}
+
+void
+UserDirectoryModel::setSearchString(const QString &f)
+{
+ userSearchString_ = f.toStdString();
+ nhlog::ui()->debug("Received user directory query: {}", userSearchString_);
+ beginResetModel();
+ results_.clear();
+ if (userSearchString_ == "")
+ nhlog::ui()->debug("Rejecting empty search string");
+ else
+ canFetchMore_ = true;
+ endResetModel();
+}
+
+void
+UserDirectoryModel::fetchMore(const QModelIndex &)
+{
+ if (!canFetchMore_)
+ return;
+
+ nhlog::net()->debug("Fetching users from mtxclient...");
+ std::string searchTerm = userSearchString_;
+ searchingUsers_ = true;
+ emit searchingUsersChanged();
+ auto job = QSharedPointer<FetchUsersFromDirectoryJob>::create();
+ connect(job.data(),
+ &FetchUsersFromDirectoryJob::fetchedSearchResults,
+ this,
+ &UserDirectoryModel::displaySearchResults);
+ http::client()->search_user_directory(
+ searchTerm,
+ [job, searchTerm](const mtx::responses::Users &res, mtx::http::RequestErr err) {
+ if (err) {
+ nhlog::net()->error("Failed to retrieve users from mtxclient - {} - {} - {}",
+ mtx::errors::to_string(err->matrix_error.errcode),
+ err->matrix_error.error,
+ err->parse_error);
+ } else {
+ emit job->fetchedSearchResults(res.results, searchTerm);
+ }
+ },
+ 50);
+}
+
+QVariant
+UserDirectoryModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() >= (int)results_.size() || index.row() < 0)
+ return {};
+ switch (role) {
+ case Roles::DisplayName:
+ return QString::fromStdString(results_[index.row()].display_name);
+ case Roles::Mxid:
+ return QString::fromStdString(results_[index.row()].user_id);
+ case Roles::AvatarUrl:
+ return QString::fromStdString(results_[index.row()].avatar_url);
+ }
+ return {};
+}
+
+void
+UserDirectoryModel::displaySearchResults(std::vector<mtx::responses::User> results, const std::string &searchTerm)
+{
+ if (searchTerm != this->userSearchString_)
+ return;
+ searchingUsers_ = false;
+ emit searchingUsersChanged();
+ if (results.empty()) {
+ nhlog::net()->debug("mtxclient helper thread yielded no results!");
+ return;
+ }
+ beginInsertRows(QModelIndex(), 0, static_cast<int>(results.size()) - 1);
+ results_ = results;
+ endInsertRows();
+ canFetchMore_ = false;
+}
diff --git a/src/UserDirectoryModel.h b/src/UserDirectoryModel.h
new file mode 100644
index 00000000..87f8163c
--- /dev/null
+++ b/src/UserDirectoryModel.h
@@ -0,0 +1,69 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+// SPDX-FileCopyrightText: 2023 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#pragma once
+
+#include <QAbstractListModel>
+#include <QString>
+#include <string>
+#include <vector>
+
+#include <mtx/responses/users.hpp>
+
+class FetchUsersFromDirectoryJob final : public QObject
+{
+ Q_OBJECT
+public:
+ explicit FetchUsersFromDirectoryJob(QObject *p = nullptr)
+ : QObject(p)
+ {
+ }
+signals:
+ void fetchedSearchResults(std::vector<mtx::responses::User> results, const std::string &searchTerm);
+};
+class UserDirectoryModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(bool searchingUsers READ searchingUsers NOTIFY searchingUsersChanged)
+
+public:
+ explicit UserDirectoryModel(QObject *parent = nullptr);
+
+ enum Roles
+ {
+ DisplayName,
+ Mxid,
+ AvatarUrl,
+ };
+ QHash<int, QByteArray> roleNames() const override;
+
+ QVariant data(const QModelIndex &index, int role) const override;
+
+ inline int rowCount(const QModelIndex &parent = QModelIndex()) const override
+ {
+ (void)parent;
+ return static_cast<int>(results_.size());
+ }
+ bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; }
+ void fetchMore(const QModelIndex &) override;
+
+private:
+ std::vector<mtx::responses::User> results_;
+ std::string userSearchString_;
+ bool searchingUsers_{false};
+ bool canFetchMore_{false};
+
+signals:
+ void searchingUsersChanged();
+
+public slots:
+ void setSearchString(const QString &f);
+ bool searchingUsers() const { return searchingUsers_; }
+
+private slots:
+ void displaySearchResults(std::vector<mtx::responses::User> results, const std::string &searchTerm);
+};
diff --git a/src/UsersModel.cpp b/src/UsersModel.cpp
index 0399bde6..5dc3b3f1 100644
--- a/src/UsersModel.cpp
+++ b/src/UsersModel.cpp
@@ -9,6 +9,7 @@
#include <QUrl>
#include "Cache.h"
+#include "Cache_p.h"
#include "CompletionModelRoles.h"
#include "UserSettingsPage.h"
@@ -16,10 +17,29 @@ UsersModel::UsersModel(const std::string &roomId, QObject *parent)
: QAbstractListModel(parent)
, room_id(roomId)
{
- roomMembers_ = cache::roomMembers(roomId);
- for (const auto &m : roomMembers_) {
- displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m)));
- userids.push_back(QString::fromStdString(m));
+ // obviously, "friends" isn't a room, but I felt this was the least invasive way
+ if (roomId == "friends") {
+ auto e = cache::client()->getAccountData(mtx::events::EventType::Direct);
+ if (e) {
+ if (auto event =
+ std::get_if<mtx::events::AccountDataEvent<mtx::events::account_data::Direct>>(
+ &e.value())) {
+ for (const auto &[userId, roomIds] : event->content.user_to_rooms) {
+ displayNames.push_back(
+ QString::fromStdString(cache::displayName(roomIds[0], userId)));
+ userids.push_back(QString::fromStdString(userId));
+ avatarUrls.push_back(cache::avatarUrl(QString::fromStdString(roomIds[0]),
+ QString::fromStdString(userId)));
+ }
+ }
+ }
+ } else {
+ for (const auto &m : cache::roomMembers(roomId)) {
+ displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m)));
+ userids.push_back(QString::fromStdString(m));
+ avatarUrls.push_back(
+ cache::avatarUrl(QString::fromStdString(room_id), QString::fromStdString(m)));
+ }
}
}
@@ -59,8 +79,7 @@ UsersModel::data(const QModelIndex &index, int role) const
case CompletionModel::SearchRole2:
return userids[index.row()];
case Roles::AvatarUrl:
- return cache::avatarUrl(QString::fromStdString(room_id),
- QString::fromStdString(roomMembers_[index.row()]));
+ return avatarUrls[index.row()];
case Roles::UserID:
return userids[index.row()].toHtmlEscaped();
}
diff --git a/src/UsersModel.h b/src/UsersModel.h
index aa71990c..525d8f0d 100644
--- a/src/UsersModel.h
+++ b/src/UsersModel.h
@@ -23,13 +23,13 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
- return (int)roomMembers_.size();
+ return (int)userids.size();
}
QVariant data(const QModelIndex &index, int role) const override;
private:
std::string room_id;
- std::vector<std::string> roomMembers_;
+ std::vector<QString> avatarUrls;
std::vector<QString> displayNames;
std::vector<QString> userids;
};
|