mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-22 19:08:58 +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/SingleImagePackModel.h
|
||||||
src/TrayIcon.cpp
|
src/TrayIcon.cpp
|
||||||
src/TrayIcon.h
|
src/TrayIcon.h
|
||||||
|
src/UserDirectoryModel.cpp
|
||||||
|
src/UserDirectoryModel.h
|
||||||
src/UserSettingsPage.cpp
|
src/UserSettingsPage.cpp
|
||||||
src/UserSettingsPage.h
|
src/UserSettingsPage.h
|
||||||
src/UsersModel.cpp
|
src/UsersModel.cpp
|
||||||
|
|
|
@ -34,6 +34,10 @@ Pane {
|
||||||
id: publicRooms
|
id: publicRooms
|
||||||
}
|
}
|
||||||
|
|
||||||
|
UserDirectoryModel {
|
||||||
|
id: userDirectory
|
||||||
|
}
|
||||||
|
|
||||||
//Timer {
|
//Timer {
|
||||||
// onTriggered: gc()
|
// onTriggered: gc()
|
||||||
// interval: 1000
|
// interval: 1000
|
||||||
|
@ -198,11 +202,15 @@ Pane {
|
||||||
}
|
}
|
||||||
|
|
||||||
function onOpenInviteUsersDialog(invitees) {
|
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,
|
"roomId": Rooms.currentRoom.roomId,
|
||||||
"plainRoomName": Rooms.currentRoom.plainRoomName,
|
"plainRoomName": Rooms.currentRoom.plainRoomName,
|
||||||
"invitees": invitees
|
"invitees": invitees
|
||||||
});
|
});
|
||||||
|
if (component.status != Component.Ready) {
|
||||||
|
console.log("Failed to create component: " + component.errorString());
|
||||||
|
}
|
||||||
dialog.show();
|
dialog.show();
|
||||||
destroyOnClose(dialog);
|
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
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
import ".."
|
import ".."
|
||||||
|
import "../components"
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
import QtQuick.Layouts 1.12
|
import QtQuick.Layouts 1.12
|
||||||
|
@ -16,17 +17,25 @@ ApplicationWindow {
|
||||||
property string roomId
|
property string roomId
|
||||||
property string plainRoomName
|
property string plainRoomName
|
||||||
property InviteesModel invitees
|
property InviteesModel invitees
|
||||||
|
property var friendsCompleter
|
||||||
|
property var profile
|
||||||
|
minimumWidth: 300
|
||||||
|
|
||||||
function addInvite() {
|
Component.onCompleted: {
|
||||||
if (inviteeEntry.isValidMxid) {
|
friendsCompleter = TimelineManager.completerFor("user", "friends")
|
||||||
invitees.addUser(inviteeEntry.text);
|
width = 600
|
||||||
inviteeEntry.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function addInvite(mxid, displayName, avatarUrl) {
|
||||||
|
if (mxid.match("@.+?:.{3,}")) {
|
||||||
|
invitees.addUser(mxid, displayName, avatarUrl);
|
||||||
|
} else
|
||||||
|
console.log("invalid mxid: " + mxid)
|
||||||
}
|
}
|
||||||
|
|
||||||
function cleanUpAndClose() {
|
function cleanUpAndClose() {
|
||||||
if (inviteeEntry.isValidMxid)
|
if (inviteeEntry.isValidMxid)
|
||||||
addInvite();
|
addInvite(inviteeEntry.text, "", "");
|
||||||
|
|
||||||
invitees.accept();
|
invitees.accept();
|
||||||
close();
|
close();
|
||||||
|
@ -53,13 +62,40 @@ ApplicationWindow {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
anchors.margins: Nheko.paddingMedium
|
anchors.margins: Nheko.paddingMedium
|
||||||
spacing: 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 {
|
Label {
|
||||||
text: qsTr("User ID to invite")
|
text: qsTr("Search user")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
color: Nheko.colors.text
|
color: Nheko.colors.text
|
||||||
}
|
}
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
spacing: Nheko.paddingMedium
|
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.")
|
placeholderText: qsTr("@joe:matrix.org", "Example user id. The name 'joe' can be localized however you want.")
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
onAccepted: {
|
onAccepted: {
|
||||||
if (isValidMxid)
|
if (isValidMxid) {
|
||||||
addInvite();
|
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()
|
Component.onCompleted: forceActiveFocus()
|
||||||
Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
|
Keys.onShortcutOverride: event.accepted = ((event.key === Qt.Key_Return || event.key === Qt.Key_Enter) && (event.modifiers & Qt.ControlModifier))
|
||||||
|
@ -83,78 +124,99 @@ ApplicationWindow {
|
||||||
cleanUpAndClose();
|
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 {
|
ToggleButton {
|
||||||
text: qsTr("Add")
|
id: searchOnServer
|
||||||
enabled: inviteeEntry.isValidMxid
|
checked: false
|
||||||
onClicked: addInvite()
|
onClicked: userSearch.model.setSearchString(inviteeEntry.text)
|
||||||
|
}
|
||||||
|
MatrixText {
|
||||||
|
text: qsTr("Search on Server")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
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 {
|
ListView {
|
||||||
id: inviteesList
|
id: inviteesList
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
model: invitees
|
model: invitees
|
||||||
|
clip: true
|
||||||
|
visible: inviteDialogRoot.width >= 500
|
||||||
|
|
||||||
delegate: ItemDelegate {
|
delegate: UserListRow {
|
||||||
id: del
|
id: del
|
||||||
|
|
||||||
hoverEnabled: true
|
hoverEnabled: true
|
||||||
width: ListView.view.width
|
width: ListView.view.width
|
||||||
height: layout.implicitHeight + Nheko.paddingSmall * 2
|
height: implicitHeight
|
||||||
onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
|
onClicked: TimelineManager.openGlobalUserProfile(model.mxid)
|
||||||
background: Rectangle {
|
|
||||||
color: del.hovered ? Nheko.colors.dark : inviteDialogRoot.color
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
userid: model.mxid
|
||||||
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
|
avatarUrl: model.avatarUrl
|
||||||
displayName: model.displayName
|
displayName: model.displayName
|
||||||
enabled: false
|
bgColor: del.hovered ? Nheko.colors.dark : inviteDialogRoot.color
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
ImageButton {
|
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"
|
image: ":/icons/icons/ui/dismiss.svg"
|
||||||
onClicked: invitees.removeUser(model.mxid)
|
onClicked: invitees.removeUser(model.mxid)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
CursorShape {
|
CursorShape {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
cursorShape: Qt.PointingHandCursor
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
@ -163,6 +225,7 @@ ApplicationWindow {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -133,6 +133,7 @@
|
||||||
<file>qml/components/ReorderableListview.qml</file>
|
<file>qml/components/ReorderableListview.qml</file>
|
||||||
<file>qml/components/SpaceMenuLevel.qml</file>
|
<file>qml/components/SpaceMenuLevel.qml</file>
|
||||||
<file>qml/components/TextButton.qml</file>
|
<file>qml/components/TextButton.qml</file>
|
||||||
|
<file>qml/components/UserListRow.qml</file>
|
||||||
<file>qml/delegates/Encrypted.qml</file>
|
<file>qml/delegates/Encrypted.qml</file>
|
||||||
<file>qml/delegates/FileMessage.qml</file>
|
<file>qml/delegates/FileMessage.qml</file>
|
||||||
<file>qml/delegates/ImageMessage.qml</file>
|
<file>qml/delegates/ImageMessage.qml</file>
|
||||||
|
|
|
@ -17,7 +17,7 @@ InviteesModel::InviteesModel(QObject *parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
InviteesModel::addUser(QString mxid)
|
InviteesModel::addUser(QString mxid, QString displayName, QString avatarUrl)
|
||||||
{
|
{
|
||||||
for (const auto &invitee : qAsConst(invitees_))
|
for (const auto &invitee : qAsConst(invitees_))
|
||||||
if (invitee->mxid_ == mxid)
|
if (invitee->mxid_ == mxid)
|
||||||
|
@ -25,7 +25,7 @@ InviteesModel::addUser(QString mxid)
|
||||||
|
|
||||||
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
|
beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
|
||||||
|
|
||||||
auto invitee = new Invitee{mxid, this};
|
auto invitee = new Invitee{mxid, displayName, avatarUrl, this};
|
||||||
auto indexOfInvitee = invitees_.count();
|
auto indexOfInvitee = invitees_.count();
|
||||||
connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
|
connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
|
||||||
emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
|
emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
|
||||||
|
@ -85,12 +85,16 @@ InviteesModel::mxids()
|
||||||
return mxidList;
|
return mxidList;
|
||||||
}
|
}
|
||||||
|
|
||||||
Invitee::Invitee(QString mxid, QObject *parent)
|
Invitee::Invitee(QString mxid, QString displayName, QString avatarUrl, QObject *parent)
|
||||||
: QObject{parent}
|
: QObject{parent}
|
||||||
, mxid_{std::move(mxid)}
|
, mxid_{std::move(mxid)}
|
||||||
{
|
{
|
||||||
|
// 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(
|
http::client()->get_profile(
|
||||||
mxid_.toStdString(), [this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
|
mxid_.toStdString(),
|
||||||
|
[this](const mtx::responses::Profile &res, mtx::http::RequestErr err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
nhlog::net()->warn("failed to retrieve profile info");
|
nhlog::net()->warn("failed to retrieve profile info");
|
||||||
emit userInfoLoaded();
|
emit userInfoLoaded();
|
||||||
|
@ -102,4 +106,9 @@ Invitee::Invitee(QString mxid, QObject *parent)
|
||||||
|
|
||||||
emit userInfoLoaded();
|
emit userInfoLoaded();
|
||||||
});
|
});
|
||||||
|
} else {
|
||||||
|
displayName_ = displayName;
|
||||||
|
avatarUrl_ = avatarUrl;
|
||||||
|
emit userInfoLoaded();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,10 @@ class Invitee final : public QObject
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Invitee(QString mxid, QObject *parent = nullptr);
|
Invitee(QString mxid,
|
||||||
|
QString displayName = "",
|
||||||
|
QString avatarUrl = "",
|
||||||
|
QObject *parent = nullptr);
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void userInfoLoaded();
|
void userInfoLoaded();
|
||||||
|
@ -44,7 +47,7 @@ public:
|
||||||
|
|
||||||
InviteesModel(QObject *parent = nullptr);
|
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);
|
Q_INVOKABLE void removeUser(QString mxid);
|
||||||
|
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
|
@ -38,6 +38,7 @@
|
||||||
#include "RoomsModel.h"
|
#include "RoomsModel.h"
|
||||||
#include "SingleImagePackModel.h"
|
#include "SingleImagePackModel.h"
|
||||||
#include "TrayIcon.h"
|
#include "TrayIcon.h"
|
||||||
|
#include "UserDirectoryModel.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
#include "UsersModel.h"
|
#include "UsersModel.h"
|
||||||
#include "Utils.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(std::vector<mtx::responses::PublicRoomsChunk>)
|
||||||
Q_DECLARE_METATYPE(mtx::responses::PublicRoom)
|
Q_DECLARE_METATYPE(mtx::responses::PublicRoom)
|
||||||
Q_DECLARE_METATYPE(mtx::responses::Profile)
|
Q_DECLARE_METATYPE(mtx::responses::Profile)
|
||||||
|
Q_DECLARE_METATYPE(mtx::responses::User)
|
||||||
|
|
||||||
MainWindow *MainWindow::instance_ = nullptr;
|
MainWindow *MainWindow::instance_ = nullptr;
|
||||||
|
|
||||||
|
@ -148,6 +150,7 @@ MainWindow::registerQmlTypes()
|
||||||
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
|
qRegisterMetaType<mtx::events::msg::KeyVerificationRequest>();
|
||||||
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
|
qRegisterMetaType<mtx::events::msg::KeyVerificationStart>();
|
||||||
qRegisterMetaType<mtx::responses::PublicRoom>();
|
qRegisterMetaType<mtx::responses::PublicRoom>();
|
||||||
|
qRegisterMetaType<mtx::responses::User>();
|
||||||
qRegisterMetaType<mtx::responses::Profile>();
|
qRegisterMetaType<mtx::responses::Profile>();
|
||||||
qRegisterMetaType<CombinedImagePackModel *>();
|
qRegisterMetaType<CombinedImagePackModel *>();
|
||||||
qRegisterMetaType<RoomSettingsAllowedRoomsModel *>();
|
qRegisterMetaType<RoomSettingsAllowedRoomsModel *>();
|
||||||
|
@ -155,7 +158,9 @@ MainWindow::registerQmlTypes()
|
||||||
qRegisterMetaType<std::vector<DeviceInfo>>();
|
qRegisterMetaType<std::vector<DeviceInfo>>();
|
||||||
|
|
||||||
qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
|
qRegisterMetaType<std::vector<mtx::responses::PublicRoomsChunk>>();
|
||||||
|
qRegisterMetaType<std::vector<mtx::responses::User>>();
|
||||||
|
|
||||||
|
qRegisterMetaType<mtx::responses::User>();
|
||||||
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
|
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
|
||||||
"im.nheko",
|
"im.nheko",
|
||||||
1,
|
1,
|
||||||
|
@ -185,6 +190,7 @@ MainWindow::registerQmlTypes()
|
||||||
qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
|
qmlRegisterType<MxcAnimatedImage>("im.nheko", 1, 0, "MxcAnimatedImage");
|
||||||
qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
|
qmlRegisterType<MxcMediaProxy>("im.nheko", 1, 0, "MxcMedia");
|
||||||
qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
|
qmlRegisterType<RoomDirectoryModel>("im.nheko", 1, 0, "RoomDirectoryModel");
|
||||||
|
qmlRegisterType<UserDirectoryModel>("im.nheko", 1, 0, "UserDirectoryModel");
|
||||||
qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login");
|
qmlRegisterType<LoginPage>("im.nheko", 1, 0, "Login");
|
||||||
qmlRegisterType<RegisterPage>("im.nheko", 1, 0, "Registration");
|
qmlRegisterType<RegisterPage>("im.nheko", 1, 0, "Registration");
|
||||||
qmlRegisterType<HiddenEvents>("im.nheko", 1, 0, "HiddenEvents");
|
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 <QUrl>
|
||||||
|
|
||||||
#include "Cache.h"
|
#include "Cache.h"
|
||||||
|
#include "Cache_p.h"
|
||||||
#include "CompletionModelRoles.h"
|
#include "CompletionModelRoles.h"
|
||||||
#include "UserSettingsPage.h"
|
#include "UserSettingsPage.h"
|
||||||
|
|
||||||
|
@ -16,10 +17,29 @@ UsersModel::UsersModel(const std::string &roomId, QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, room_id(roomId)
|
, room_id(roomId)
|
||||||
{
|
{
|
||||||
roomMembers_ = cache::roomMembers(roomId);
|
// obviously, "friends" isn't a room, but I felt this was the least invasive way
|
||||||
for (const auto &m : roomMembers_) {
|
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)));
|
displayNames.push_back(QString::fromStdString(cache::displayName(room_id, m)));
|
||||||
userids.push_back(QString::fromStdString(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:
|
case CompletionModel::SearchRole2:
|
||||||
return userids[index.row()];
|
return userids[index.row()];
|
||||||
case Roles::AvatarUrl:
|
case Roles::AvatarUrl:
|
||||||
return cache::avatarUrl(QString::fromStdString(room_id),
|
return avatarUrls[index.row()];
|
||||||
QString::fromStdString(roomMembers_[index.row()]));
|
|
||||||
case Roles::UserID:
|
case Roles::UserID:
|
||||||
return userids[index.row()].toHtmlEscaped();
|
return userids[index.row()].toHtmlEscaped();
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,13 +23,13 @@ public:
|
||||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
int rowCount(const QModelIndex &parent = QModelIndex()) const override
|
||||||
{
|
{
|
||||||
(void)parent;
|
(void)parent;
|
||||||
return (int)roomMembers_.size();
|
return (int)userids.size();
|
||||||
}
|
}
|
||||||
QVariant data(const QModelIndex &index, int role) const override;
|
QVariant data(const QModelIndex &index, int role) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::string room_id;
|
std::string room_id;
|
||||||
std::vector<std::string> roomMembers_;
|
std::vector<QString> avatarUrls;
|
||||||
std::vector<QString> displayNames;
|
std::vector<QString> displayNames;
|
||||||
std::vector<QString> userids;
|
std::vector<QString> userids;
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue