mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 12:38:48 +03:00
add user search to invite dialog (#1253)
This commit is contained in:
parent
3abb49c4a2
commit
5ed3bfc8f8
12 changed files with 437 additions and 99 deletions
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
53
resources/qml/components/UserListRow.qml
Normal file
53
resources/qml/components/UserListRow.qml
Normal file
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -133,6 +133,7 @@
|
|||
<file>qml/components/ReorderableListview.qml</file>
|
||||
<file>qml/components/SpaceMenuLevel.qml</file>
|
||||
<file>qml/components/TextButton.qml</file>
|
||||
<file>qml/components/UserListRow.qml</file>
|
||||
<file>qml/delegates/Encrypted.qml</file>
|
||||
<file>qml/delegates/FileMessage.qml</file>
|
||||
<file>qml/delegates/ImageMessage.qml</file>
|
||||
|
|
|
@ -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");
|
||||
// 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();
|
||||
return;
|
||||
}
|
||||
|
||||
displayName_ = QString::fromStdString(res.display_name);
|
||||
avatarUrl_ = QString::fromStdString(res.avatar_url);
|
||||
|
||||
emit userInfoLoaded();
|
||||
});
|
||||
});
|
||||
} else {
|
||||
displayName_ = displayName;
|
||||
avatarUrl_ = avatarUrl;
|
||||
emit userInfoLoaded();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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");
|
||||
|
|
105
src/UserDirectoryModel.cpp
Normal file
105
src/UserDirectoryModel.cpp
Normal file
|
@ -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;
|
||||
}
|
69
src/UserDirectoryModel.h
Normal file
69
src/UserDirectoryModel.h
Normal file
|
@ -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);
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue