diff --git a/resources/qml/MatrixTextField.qml b/resources/qml/MatrixTextField.qml
index fec3f6b4..3f0f77ed 100644
--- a/resources/qml/MatrixTextField.qml
+++ b/resources/qml/MatrixTextField.qml
@@ -117,12 +117,12 @@ ColumnLayout {
palette: Nheko.colors
color: labelC.color
opacity: labelC.text ? 0 : 1
+ focus: true
onTextEdited: c.textEdited()
onAccepted: c.accepted()
onEditingFinished: c.editingFinished()
-
background: Rectangle {
id: backgroundRect
diff --git a/resources/qml/dialogs/RoomMembers.qml b/resources/qml/dialogs/RoomMembers.qml
index 55d5488b..8a12e5bc 100644
--- a/resources/qml/dialogs/RoomMembers.qml
+++ b/resources/qml/dialogs/RoomMembers.qml
@@ -63,6 +63,37 @@ ApplicationWindow {
onClicked: TimelineManager.openInviteUsers(members.roomId)
}
+ MatrixTextField {
+ id: searchBar
+
+ Layout.fillWidth: true
+ placeholderText: qsTr("Search...")
+ onTextChanged: members.setFilterString(text)
+
+ Component.onCompleted: forceActiveFocus()
+ }
+
+ RowLayout {
+ spacing: Nheko.paddingMedium
+
+ Label {
+ text: qsTr("Sort by: ")
+ color: Nheko.colors.text
+ }
+
+ ComboBox {
+ model: ListModel {
+ ListElement { data: MemberList.Mxid; text: qsTr("User ID") }
+ ListElement { data: MemberList.DisplayName; text: qsTr("Display name") }
+ ListElement { data: MemberList.Powerlevel; text: qsTr("Power level") }
+ }
+ textRole: "text"
+ valueRole: "data"
+ onCurrentValueChanged: members.sortBy(currentValue)
+ Layout.fillWidth: true
+ }
+ }
+
ScrollView {
palette: Nheko.colors
padding: Nheko.paddingMedium
@@ -172,14 +203,14 @@ ApplicationWindow {
width: parent.width
visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
// use the default height if it's visible, otherwise no height at all
- height: membersLoadingSpinner.height
+ height: membersLoadingSpinner.implicitHeight
anchors.margins: Nheko.paddingMedium
Spinner {
id: membersLoadingSpinner
anchors.centerIn: parent
- height: visible ? 35 : 0
+ implicitHeight: parent.visible ? 35 : 0
}
}
diff --git a/src/MemberList.cpp b/src/MemberList.cpp
index fb4ae76b..f5154da4 100644
--- a/src/MemberList.cpp
+++ b/src/MemberList.cpp
@@ -6,15 +6,20 @@
#include "MemberList.h"
#include "Cache.h"
+#include "Cache_p.h"
#include "ChatPage.h"
#include "Config.h"
#include "Logging.h"
#include "Utils.h"
#include "timeline/TimelineViewManager.h"
-MemberList::MemberList(const QString &room_id, QObject *parent)
+MemberListBackend::MemberListBackend(const QString &room_id, QObject *parent)
: QAbstractListModel{parent}
, room_id_{room_id}
+ , powerLevels_{cache::client()
+ ->getStateEvent<mtx::events::state::PowerLevels>(room_id_.toStdString())
+ .value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
+ .content}
{
try {
info_ = cache::singleRoomInfo(room_id_.toStdString());
@@ -23,7 +28,8 @@ MemberList::MemberList(const QString &room_id, QObject *parent)
}
try {
- auto members = cache::getMembers(room_id_.toStdString());
+ // HACK: due to QTBUG-1020169, we'll load a big chunk to speed things up
+ auto members = cache::getMembers(room_id_.toStdString(), 0, -1);
addUsers(members);
numUsersLoaded_ = members.size();
} catch (const lmdb::error &e) {
@@ -32,7 +38,7 @@ MemberList::MemberList(const QString &room_id, QObject *parent)
}
void
-MemberList::addUsers(const std::vector<RoomMember> &members)
+MemberListBackend::addUsers(const std::vector<RoomMember> &members)
{
beginInsertRows(QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1);
@@ -46,7 +52,7 @@ MemberList::addUsers(const std::vector<RoomMember> &members)
}
QHash<int, QByteArray>
-MemberList::roleNames() const
+MemberListBackend::roleNames() const
{
return {
{Mxid, "mxid"},
@@ -57,7 +63,7 @@ MemberList::roleNames() const
}
QVariant
-MemberList::data(const QModelIndex &index, int role) const
+MemberListBackend::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0)
return {};
@@ -80,13 +86,16 @@ MemberList::data(const QModelIndex &index, int role) const
else
return stat->user_verified;
}
+ case Powerlevel:
+ return static_cast<qlonglong>(
+ powerLevels_.user_level(m_memberList[index.row()].first.user_id.toStdString()));
default:
return {};
}
}
bool
-MemberList::canFetchMore(const QModelIndex &) const
+MemberListBackend::canFetchMore(const QModelIndex &) const
{
const size_t numMembers = rowCount();
if (numMembers > 1 && numMembers < info_.member_count)
@@ -96,7 +105,7 @@ MemberList::canFetchMore(const QModelIndex &) const
}
void
-MemberList::fetchMore(const QModelIndex &)
+MemberListBackend::fetchMore(const QModelIndex &)
{
loadingMoreMembers_ = true;
emit loadingMoreMembersChanged();
@@ -109,3 +118,49 @@ MemberList::fetchMore(const QModelIndex &)
loadingMoreMembers_ = false;
emit loadingMoreMembersChanged();
}
+
+MemberList::MemberList(const QString &room_id, QObject *parent)
+ : QSortFilterProxyModel{parent}
+ , m_model{room_id, this}
+{
+ connect(&m_model, &MemberListBackend::roomNameChanged, this, &MemberList::roomNameChanged);
+ connect(
+ &m_model, &MemberListBackend::memberCountChanged, this, &MemberList::memberCountChanged);
+ connect(&m_model, &MemberListBackend::avatarUrlChanged, this, &MemberList::avatarUrlChanged);
+ connect(&m_model, &MemberListBackend::roomIdChanged, this, &MemberList::roomIdChanged);
+ connect(&m_model,
+ &MemberListBackend::numUsersLoadedChanged,
+ this,
+ &MemberList::numUsersLoadedChanged);
+ connect(&m_model,
+ &MemberListBackend::loadingMoreMembersChanged,
+ this,
+ &MemberList::loadingMoreMembersChanged);
+
+ setSourceModel(&m_model);
+ setSortRole(MemberSortRoles::Mxid);
+ sort(0, Qt::AscendingOrder);
+ setDynamicSortFilter(true);
+ setFilterCaseSensitivity(Qt::CaseInsensitive);
+}
+
+void
+MemberList::setFilterString(const QString &text)
+{
+ setFilterRegExp(QRegExp::escape(text));
+}
+
+void
+MemberList::sortBy(const MemberSortRoles role)
+{
+ setSortRole(role);
+ // Unfortunately, Qt doesn't provide a "setSortOrder" function.
+ sort(0, role == MemberSortRoles::Powerlevel ? Qt::DescendingOrder : Qt::AscendingOrder);
+}
+
+bool
+MemberList::filterAcceptsRow(int source_row, const QModelIndex &) const
+{
+ return m_model.m_memberList[source_row].first.user_id.contains(filterRegExp()) ||
+ m_model.m_memberList[source_row].first.display_name.contains(filterRegExp());
+}
diff --git a/src/MemberList.h b/src/MemberList.h
index be345d41..2f90e5e8 100644
--- a/src/MemberList.h
+++ b/src/MemberList.h
@@ -6,10 +6,13 @@
#pragma once
#include <QAbstractListModel>
+#include <QSortFilterProxyModel>
+
+#include <mtx/events/power_levels.hpp>
#include "CacheStructs.h"
-class MemberList : public QAbstractListModel
+class MemberListBackend : public QAbstractListModel
{
Q_OBJECT
@@ -27,8 +30,10 @@ public:
DisplayName,
AvatarUrl,
Trustlevel,
+ Powerlevel,
};
- MemberList(const QString &room_id, QObject *parent = nullptr);
+
+ MemberListBackend(const QString &room_id, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
@@ -66,4 +71,56 @@ private:
RoomInfo info_;
int numUsersLoaded_{0};
bool loadingMoreMembers_{false};
+
+ mtx::events::state::PowerLevels powerLevels_;
+
+ friend class MemberList;
+};
+
+class MemberList : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
+ Q_PROPERTY(int memberCount READ memberCount NOTIFY memberCountChanged)
+ Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
+ Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged)
+ Q_PROPERTY(int numUsersLoaded READ numUsersLoaded NOTIFY numUsersLoadedChanged)
+ Q_PROPERTY(bool loadingMoreMembers READ loadingMoreMembers NOTIFY loadingMoreMembersChanged)
+
+public:
+ enum MemberSortRoles
+ {
+ Mxid = MemberListBackend::Roles::Mxid,
+ DisplayName = MemberListBackend::Roles::DisplayName,
+ Powerlevel = MemberListBackend::Roles::Powerlevel,
+ };
+ Q_ENUM(MemberSortRoles)
+
+ MemberList(const QString &room_id, QObject *parent = nullptr);
+
+ QString roomName() const { return m_model.roomName(); }
+ int memberCount() const { return m_model.memberCount(); }
+ QString avatarUrl() const { return m_model.avatarUrl(); }
+ QString roomId() const { return m_model.roomId(); }
+ int numUsersLoaded() const { return m_model.numUsersLoaded(); }
+ bool loadingMoreMembers() const { return m_model.loadingMoreMembers(); }
+
+signals:
+ void roomNameChanged();
+ void memberCountChanged();
+ void avatarUrlChanged();
+ void roomIdChanged();
+ void numUsersLoadedChanged();
+ void loadingMoreMembersChanged();
+
+public slots:
+ void setFilterString(const QString &text);
+ void sortBy(const MemberSortRoles role);
+
+protected:
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
+
+private:
+ MemberListBackend m_model;
};
|