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: [PATCH] add user search to invite dialog (#1253)
---
CMakeLists.txt | 2 +
resources/qml/Root.qml | 10 +-
resources/qml/components/UserListRow.qml | 53 ++++++
resources/qml/dialogs/InviteDialog.qml | 211 +++++++++++++++--------
resources/res.qrc | 1 +
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 +-
12 files changed, 438 insertions(+), 100 deletions(-)
create mode 100644 resources/qml/components/UserListRow.qml
create mode 100644 src/UserDirectoryModel.cpp
create mode 100644 src/UserDirectoryModel.h
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 242744eb..d4724a1c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -491,6 +491,8 @@ set(SRC_FILES
src/SingleImagePackModel.h
src/TrayIcon.cpp
src/TrayIcon.h
+ src/UserDirectoryModel.cpp
+ src/UserDirectoryModel.h
src/UserSettingsPage.cpp
src/UserSettingsPage.h
src/UsersModel.cpp
diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml
index 5f7d7229..f60ebac1 100644
--- a/resources/qml/Root.qml
+++ b/resources/qml/Root.qml
@@ -34,6 +34,10 @@ Pane {
id: publicRooms
}
+ UserDirectoryModel {
+ id: userDirectory
+ }
+
//Timer {
// onTriggered: gc()
// interval: 1000
@@ -198,11 +202,15 @@ Pane {
}
function onOpenInviteUsersDialog(invitees) {
- var dialog = Qt.createComponent("qrc:/qml/dialogs/InviteDialog.qml").createObject(timelineRoot, {
+ var component = Qt.createComponent("qrc:/qml/dialogs/InviteDialog.qml")
+ var dialog = component.createObject(timelineRoot, {
"roomId": Rooms.currentRoom.roomId,
"plainRoomName": Rooms.currentRoom.plainRoomName,
"invitees": invitees
});
+ if (component.status != Component.Ready) {
+ console.log("Failed to create component: " + component.errorString());
+ }
dialog.show();
destroyOnClose(dialog);
}
diff --git a/resources/qml/components/UserListRow.qml b/resources/qml/components/UserListRow.qml
new file mode 100644
index 00000000..8cbbd195
--- /dev/null
+++ b/resources/qml/components/UserListRow.qml
@@ -0,0 +1,53 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+// SPDX-FileCopyrightText: 2022 Nheko Contributors
+// SPDX-FileCopyrightText: 2023 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+import ".."
+import QtQuick 2.12
+import QtQuick.Controls 2.12
+import QtQuick.Layouts 1.12
+import im.nheko 1.0
+
+ItemDelegate {
+ property alias bgColor: background.color
+ property alias userid: avatar.userid
+ property alias displayName: avatar.displayName
+ property string avatarUrl
+ implicitHeight: layout.implicitHeight + Nheko.paddingSmall * 2
+ background: Rectangle {id: background}
+ GridLayout {
+ id: layout
+ anchors.centerIn: parent
+ width: parent.width - Nheko.paddingSmall * 2
+ rows: 2
+ columns: 2
+ rowSpacing: Nheko.paddingSmall
+ columnSpacing: Nheko.paddingMedium
+
+ Avatar {
+ id: avatar
+ Layout.rowSpan: 2
+ Layout.preferredWidth: Nheko.avatarSize
+ Layout.preferredHeight: Nheko.avatarSize
+ Layout.alignment: Qt.AlignLeft
+ url: avatarUrl.replace("mxc://", "image://MxcImage/")
+ enabled: false
+ }
+ Label {
+ Layout.fillWidth: true
+ text: displayName
+ color: TimelineManager.userColor(userid, Nheko.colors.window)
+ font.pointSize: fontMetrics.font.pointSize
+ }
+
+ Label {
+ Layout.fillWidth: true
+ Layout.alignment: Qt.AlignTop
+ text: userid
+ color: Nheko.colors.buttonText
+ font.pointSize: fontMetrics.font.pointSize * 0.9
+ }
+ }
+}
diff --git a/resources/qml/dialogs/InviteDialog.qml b/resources/qml/dialogs/InviteDialog.qml
index 33fd32c4..20699a65 100644
--- a/resources/qml/dialogs/InviteDialog.qml
+++ b/resources/qml/dialogs/InviteDialog.qml
@@ -5,6 +5,7 @@
// SPDX-License-Identifier: GPL-3.0-or-later
import ".."
+import "../components"
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
@@ -16,17 +17,25 @@ ApplicationWindow {
property string roomId
property string plainRoomName
property InviteesModel invitees
+ property var friendsCompleter
+ property var profile
+ minimumWidth: 300
- function addInvite() {
- if (inviteeEntry.isValidMxid) {
- invitees.addUser(inviteeEntry.text);
- inviteeEntry.clear();
- }
+ Component.onCompleted: {
+ friendsCompleter = TimelineManager.completerFor("user", "friends")
+ width = 600
+ }
+
+ function addInvite(mxid, displayName, avatarUrl) {
+ if (mxid.match("@.+?:.{3,}")) {
+ invitees.addUser(mxid, displayName, avatarUrl);
+ } else
+ console.log("invalid mxid: " + mxid)
}
function cleanUpAndClose() {
if (inviteeEntry.isValidMxid)
- addInvite();
+ addInvite(inviteeEntry.text, "", "");
invitees.accept();
close();
@@ -53,13 +62,40 @@ ApplicationWindow {
anchors.fill: parent
anchors.margins: Nheko.paddingMedium
spacing: Nheko.paddingMedium
+ Flow {
+ layoutDirection: Qt.LeftToRight
+ Layout.fillWidth: true
+ Layout.preferredHeight: implicitHeight
+ spacing: 4
+ visible: !inviteesList.visible
+ Repeater {
+ id: inviteesRepeater
+ model: invitees
+ delegate: ItemDelegate {
+ onClicked: invitees.removeUser(model.mxid)
+ id: inviteeButton
+ contentItem: Label {
+ anchors.centerIn: parent
+ id: inviteeUserid
+ text: model.displayName != "" ? model.displayName : model.userid
+ color: inviteeButton.hovered ? Nheko.colors.highlightedText: Nheko.colors.text
+ maximumLineCount: 1
+ }
+ background: Rectangle {
+ border.color: Nheko.colors.text
+ color: inviteeButton.hovered ? Nheko.colors.highlight : Nheko.colors.window
+ border.width: 1
+ radius: inviteeButton.height / 2
+ }
+ }
+ }
+ }
Label {
- text: qsTr("User ID to invite")
+ text: qsTr("Search user")
Layout.fillWidth: true
color: Nheko.colors.text
}
-
RowLayout {
spacing: Nheko.paddingMedium
@@ -72,9 +108,14 @@ ApplicationWindow {
placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.")
Layout.fillWidth: true
onAccepted: {
- if (isValidMxid)
- addInvite();
-
+ if (isValidMxid) {
+ addInvite(text, "", "");
+ clear()
+ }
+ else if (userSearch.count > 0) {
+ addInvite(userSearch.itemAtIndex(0).userid, userSearch.itemAtIndex(0).displayName, userSearch.itemAtIndex(0).avatarUrl)
+ clear()
+ }
}
Component.onCompleted: forceActiveFocus()
Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
@@ -83,85 +124,107 @@ ApplicationWindow {
cleanUpAndClose();
}
+ onTextChanged: {
+ searchTimer.restart()
+ if(isValidMxid) {
+ profile = TimelineManager.getGlobalUserProfile(text);
+ } else
+ profile = null;
+ }
+ Timer {
+ id: searchTimer
+
+ interval: 350
+ onTriggered: {
+ userSearch.model.setSearchString(parent.text)
+ }
+ }
}
- Button {
- text: qsTr("Add")
- enabled: inviteeEntry.isValidMxid
- onClicked: addInvite()
+ ToggleButton {
+ id: searchOnServer
+ checked: false
+ onClicked: userSearch.model.setSearchString(inviteeEntry.text)
+ }
+ MatrixText {
+ text: qsTr("Search on Server")
}
}
-
- ListView {
- id: inviteesList
-
- Layout.fillWidth: true
- Layout.fillHeight: true
- model: invitees
-
- delegate: ItemDelegate {
- id: del
-
- hoverEnabled: true
- width: ListView.view.width
- height: layout.implicitHeight + Nheko.paddingSmall * 2
- onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
- background: Rectangle {
- color: del.hovered ? Nheko.colors.dark : inviteDialogRoot.color
+ RowLayout {
+ UserListRow {
+ visible: inviteeEntry.isValidMxid
+ id: del3
+ Layout.preferredWidth: inviteDialogRoot.width/2
+ Layout.alignment: Qt.AlignTop
+ Layout.preferredHeight: implicitHeight
+ displayName: profile? profile.displayName : ""
+ avatarUrl: profile? profile.avatarUrl : ""
+ userid: inviteeEntry.text
+ onClicked: addInvite(inviteeEntry.text, displayName, avatarUrl)
+ bgColor: del3.hovered ? Nheko.colors.dark : inviteDialogRoot.color
+ }
+ ListView {
+ visible: !inviteeEntry.isValidMxid
+ id: userSearch
+ model: searchOnServer.checked? userDirectory : friendsCompleter
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ clip: true
+ delegate: UserListRow {
+ id: del2
+ width: ListView.view.width
+ height: implicitHeight
+ displayName: model.displayName
+ userid: model.userid
+ avatarUrl: model.avatarUrl
+ onClicked: addInvite(userid, displayName, avatarUrl)
+ bgColor: del2.hovered ? Nheko.colors.dark : inviteDialogRoot.color
}
+ }
+ Rectangle {
+ Layout.fillHeight: true
+ visible: inviteesList.visible
+ width: 1
+ color: Nheko.theme.separator
+ }
+ ListView {
+ id: inviteesList
- RowLayout {
- id: layout
-
- spacing: Nheko.paddingMedium
- anchors.centerIn: parent
- width: del.width - Nheko.paddingSmall * 2
-
- Avatar {
- width: Nheko.avatarSize
- height: Nheko.avatarSize
- userid: model.mxid
- url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
- displayName: model.displayName
- enabled: false
- }
-
- ColumnLayout {
- spacing: Nheko.paddingSmall
-
- Label {
- text: model.displayName
- color: TimelineManager.userColor(model ? model.mxid : "", del.background.color)
- font.pointSize: fontMetrics.font.pointSize
- }
-
- Label {
- text: model.mxid
- color: del.hovered ? Nheko.colors.brightText : Nheko.colors.buttonText
- font.pointSize: fontMetrics.font.pointSize * 0.9
- }
-
- }
-
- Item {
- Layout.fillWidth: true
- }
-
+ Layout.fillWidth: true
+ Layout.fillHeight: true
+ model: invitees
+ clip: true
+ visible: inviteDialogRoot.width >= 500
+
+ delegate: UserListRow {
+ id: del
+ hoverEnabled: true
+ width: ListView.view.width
+ height: implicitHeight
+ onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
+ userid: model.mxid
+ avatarUrl: model.avatarUrl
+ displayName: model.displayName
+ bgColor: del.hovered ? Nheko.colors.dark : inviteDialogRoot.color
ImageButton {
+ anchors.right: parent.right
+ anchors.rightMargin: Nheko.paddingSmall
+ anchors.top: parent.top
+ anchors.topMargin: Nheko.paddingSmall
+ id: removeButton
image: ":/icons/icons/ui/dismiss.svg"
onClicked: invitees.removeUser(model.mxid)
}
- }
+ CursorShape {
+ anchors.fill: parent
+ cursorShape: Qt.PointingHandCursor
+ }
- CursorShape {
- anchors.fill: parent
- cursorShape: Qt.PointingHandCursor
}
}
-
}
}
diff --git a/resources/res.qrc b/resources/res.qrc
index 297cd3e9..9eca9a98 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -133,6 +133,7 @@
qml/components/ReorderableListview.qml
qml/components/SpaceMenuLevel.qml
qml/components/TextButton.qml
+ qml/components/UserListRow.qml
qml/delegates/Encrypted.qml
qml/delegates/FileMessage.qml
qml/delegates/ImageMessage.qml
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;
};