Search room members (#1049)

This commit is contained in:
Loren Burkholder 2022-04-22 19:59:40 -04:00 committed by GitHub
parent c637989ac0
commit 6672e765d7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 155 additions and 12 deletions

View file

@ -117,12 +117,12 @@ ColumnLayout {
palette: Nheko.colors palette: Nheko.colors
color: labelC.color color: labelC.color
opacity: labelC.text ? 0 : 1 opacity: labelC.text ? 0 : 1
focus: true
onTextEdited: c.textEdited() onTextEdited: c.textEdited()
onAccepted: c.accepted() onAccepted: c.accepted()
onEditingFinished: c.editingFinished() onEditingFinished: c.editingFinished()
background: Rectangle { background: Rectangle {
id: backgroundRect id: backgroundRect

View file

@ -63,6 +63,37 @@ ApplicationWindow {
onClicked: TimelineManager.openInviteUsers(members.roomId) 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 { ScrollView {
palette: Nheko.colors palette: Nheko.colors
padding: Nheko.paddingMedium padding: Nheko.paddingMedium
@ -172,14 +203,14 @@ ApplicationWindow {
width: parent.width width: parent.width
visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers visible: (members.numUsersLoaded < members.memberCount) && members.loadingMoreMembers
// use the default height if it's visible, otherwise no height at all // use the default height if it's visible, otherwise no height at all
height: membersLoadingSpinner.height height: membersLoadingSpinner.implicitHeight
anchors.margins: Nheko.paddingMedium anchors.margins: Nheko.paddingMedium
Spinner { Spinner {
id: membersLoadingSpinner id: membersLoadingSpinner
anchors.centerIn: parent anchors.centerIn: parent
height: visible ? 35 : 0 implicitHeight: parent.visible ? 35 : 0
} }
} }

View file

@ -6,15 +6,20 @@
#include "MemberList.h" #include "MemberList.h"
#include "Cache.h" #include "Cache.h"
#include "Cache_p.h"
#include "ChatPage.h" #include "ChatPage.h"
#include "Config.h" #include "Config.h"
#include "Logging.h" #include "Logging.h"
#include "Utils.h" #include "Utils.h"
#include "timeline/TimelineViewManager.h" #include "timeline/TimelineViewManager.h"
MemberList::MemberList(const QString &room_id, QObject *parent) MemberListBackend::MemberListBackend(const QString &room_id, QObject *parent)
: QAbstractListModel{parent} : QAbstractListModel{parent}
, room_id_{room_id} , 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 { try {
info_ = cache::singleRoomInfo(room_id_.toStdString()); info_ = cache::singleRoomInfo(room_id_.toStdString());
@ -23,7 +28,8 @@ MemberList::MemberList(const QString &room_id, QObject *parent)
} }
try { 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); addUsers(members);
numUsersLoaded_ = members.size(); numUsersLoaded_ = members.size();
} catch (const lmdb::error &e) { } catch (const lmdb::error &e) {
@ -32,7 +38,7 @@ MemberList::MemberList(const QString &room_id, QObject *parent)
} }
void 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); 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> QHash<int, QByteArray>
MemberList::roleNames() const MemberListBackend::roleNames() const
{ {
return { return {
{Mxid, "mxid"}, {Mxid, "mxid"},
@ -57,7 +63,7 @@ MemberList::roleNames() const
} }
QVariant 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) if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0)
return {}; return {};
@ -80,13 +86,16 @@ MemberList::data(const QModelIndex &index, int role) const
else else
return stat->user_verified; return stat->user_verified;
} }
case Powerlevel:
return static_cast<qlonglong>(
powerLevels_.user_level(m_memberList[index.row()].first.user_id.toStdString()));
default: default:
return {}; return {};
} }
} }
bool bool
MemberList::canFetchMore(const QModelIndex &) const MemberListBackend::canFetchMore(const QModelIndex &) const
{ {
const size_t numMembers = rowCount(); const size_t numMembers = rowCount();
if (numMembers > 1 && numMembers < info_.member_count) if (numMembers > 1 && numMembers < info_.member_count)
@ -96,7 +105,7 @@ MemberList::canFetchMore(const QModelIndex &) const
} }
void void
MemberList::fetchMore(const QModelIndex &) MemberListBackend::fetchMore(const QModelIndex &)
{ {
loadingMoreMembers_ = true; loadingMoreMembers_ = true;
emit loadingMoreMembersChanged(); emit loadingMoreMembersChanged();
@ -109,3 +118,49 @@ MemberList::fetchMore(const QModelIndex &)
loadingMoreMembers_ = false; loadingMoreMembers_ = false;
emit loadingMoreMembersChanged(); 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());
}

View file

@ -6,10 +6,13 @@
#pragma once #pragma once
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QSortFilterProxyModel>
#include <mtx/events/power_levels.hpp>
#include "CacheStructs.h" #include "CacheStructs.h"
class MemberList : public QAbstractListModel class MemberListBackend : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
@ -27,8 +30,10 @@ public:
DisplayName, DisplayName,
AvatarUrl, AvatarUrl,
Trustlevel, Trustlevel,
Powerlevel,
}; };
MemberList(const QString &room_id, QObject *parent = nullptr);
MemberListBackend(const QString &room_id, QObject *parent = nullptr);
QHash<int, QByteArray> roleNames() const override; QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override int rowCount(const QModelIndex &parent = QModelIndex()) const override
@ -66,4 +71,56 @@ private:
RoomInfo info_; RoomInfo info_;
int numUsersLoaded_{0}; int numUsersLoaded_{0};
bool loadingMoreMembers_{false}; 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;
}; };