From 5ed3bfc8f82e9123db4c198b6b9701df57c968f4 Mon Sep 17 00:00:00 2001 From: Malte E <97891689+maltee1@users.noreply.github.com> Date: Wed, 1 Feb 2023 00:59:49 +0800 Subject: add user search to invite dialog (#1253) --- src/InviteesModel.cpp | 39 ++++++++++------- src/InviteesModel.h | 7 ++- src/MainWindow.cpp | 6 +++ src/UserDirectoryModel.cpp | 105 +++++++++++++++++++++++++++++++++++++++++++++ src/UserDirectoryModel.h | 69 +++++++++++++++++++++++++++++ src/UsersModel.cpp | 31 ++++++++++--- src/UsersModel.h | 4 +- 7 files changed, 236 insertions(+), 25 deletions(-) create mode 100644 src/UserDirectoryModel.cpp create mode 100644 src/UserDirectoryModel.h (limited to 'src') 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; - } - - displayName_ = QString::fromStdString(res.display_name); - avatarUrl_ = QString::fromStdString(res.avatar_url); + // 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); - 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 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) Q_DECLARE_METATYPE(std::vector) 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(); qRegisterMetaType(); qRegisterMetaType(); + qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); qRegisterMetaType(); @@ -155,7 +158,9 @@ MainWindow::registerQmlTypes() qRegisterMetaType>(); qRegisterMetaType>(); + qRegisterMetaType>(); + qRegisterMetaType(); qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject, "im.nheko", 1, @@ -185,6 +190,7 @@ MainWindow::registerQmlTypes() qmlRegisterType("im.nheko", 1, 0, "MxcAnimatedImage"); qmlRegisterType("im.nheko", 1, 0, "MxcMedia"); qmlRegisterType("im.nheko", 1, 0, "RoomDirectoryModel"); + qmlRegisterType("im.nheko", 1, 0, "UserDirectoryModel"); qmlRegisterType("im.nheko", 1, 0, "Login"); qmlRegisterType("im.nheko", 1, 0, "Registration"); qmlRegisterType("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 +#include "MatrixClient.h" +#include "mtx/responses/users.hpp" + +UserDirectoryModel::UserDirectoryModel(QObject *parent) + : QAbstractListModel{parent} +{ +} + +QHash +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::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 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(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 +#include +#include +#include + +#include + +class FetchUsersFromDirectoryJob final : public QObject +{ + Q_OBJECT +public: + explicit FetchUsersFromDirectoryJob(QObject *p = nullptr) + : QObject(p) + { + } +signals: + void fetchedSearchResults(std::vector 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 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(results_.size()); + } + bool canFetchMore(const QModelIndex &) const override { return canFetchMore_; } + void fetchMore(const QModelIndex &) override; + +private: + std::vector 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 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 #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>( + &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 roomMembers_; + std::vector avatarUrls; std::vector displayNames; std::vector userids; }; -- cgit 1.4.1