QML the room member list

This commit is contained in:
Loren Burkholder 2021-05-29 21:09:21 -04:00
parent 88ed0fade7
commit 77a0c574bf
15 changed files with 284 additions and 225 deletions

View file

@ -285,7 +285,6 @@ set(SRC_FILES
src/dialogs/JoinRoom.cpp src/dialogs/JoinRoom.cpp
src/dialogs/LeaveRoom.cpp src/dialogs/LeaveRoom.cpp
src/dialogs/Logout.cpp src/dialogs/Logout.cpp
src/dialogs/MemberList.cpp
src/dialogs/PreviewUploadOverlay.cpp src/dialogs/PreviewUploadOverlay.cpp
src/dialogs/ReCaptcha.cpp src/dialogs/ReCaptcha.cpp
src/dialogs/ReadReceipts.cpp src/dialogs/ReadReceipts.cpp
@ -351,6 +350,7 @@ set(SRC_FILES
src/LoginPage.cpp src/LoginPage.cpp
src/MainWindow.cpp src/MainWindow.cpp
src/MatrixClient.cpp src/MatrixClient.cpp
src/MemberList.cpp
src/MxcImageProvider.cpp src/MxcImageProvider.cpp
src/Olm.cpp src/Olm.cpp
src/RegisterPage.cpp src/RegisterPage.cpp
@ -496,7 +496,6 @@ qt5_wrap_cpp(MOC_HEADERS
src/dialogs/JoinRoom.h src/dialogs/JoinRoom.h
src/dialogs/LeaveRoom.h src/dialogs/LeaveRoom.h
src/dialogs/Logout.h src/dialogs/Logout.h
src/dialogs/MemberList.h
src/dialogs/PreviewUploadOverlay.h src/dialogs/PreviewUploadOverlay.h
src/dialogs/RawMessage.h src/dialogs/RawMessage.h
src/dialogs/ReCaptcha.h src/dialogs/ReCaptcha.h
@ -557,6 +556,7 @@ qt5_wrap_cpp(MOC_HEADERS
src/InviteeItem.h src/InviteeItem.h
src/LoginPage.h src/LoginPage.h
src/MainWindow.h src/MainWindow.h
src/MemberList.h
src/MxcImageProvider.h src/MxcImageProvider.h
src/RegisterPage.h src/RegisterPage.h
src/SSOHandler.h src/SSOHandler.h

View file

@ -0,0 +1,111 @@
import QtQuick 2.12
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import QtQuick.Window 2.12
import im.nheko 1.0
ApplicationWindow {
id: roomMembersRoot
property string roomName: Rooms.currentRoom.roomName
property MemberList members
title: qsTr("Members of ") + roomName
x: MainWindow.x + (MainWindow.width / 2) - (width / 2)
y: MainWindow.y + (MainWindow.height / 2) - (height / 2)
height: 650
width: 420
minimumHeight: 420
Shortcut {
sequence: StandardKey.Cancel
onActivated: roomMembersRoot.close()
}
ColumnLayout {
anchors.fill: parent
anchors.margins: 10
spacing: 10
Avatar {
id: roomAvatar
width: 130
height: width
displayName: members.roomName
Layout.alignment: Qt.AlignHCenter
url: members.avatarUrl.replace("mxc://", "image://MxcImage/")
onClicked: TimelineManager.timeline.openRoomSettings(members.roomId)
}
Label {
font.pixelSize: 24
text: members.memberCount + (members.memberCount === 1 ? qsTr(" person in ") : qsTr(" people in ")) + roomName
Layout.alignment: Qt.AlignHCenter
}
ScrollView {
clip: false
palette: colors
padding: 10
ScrollBar.horizontal.visible: false
Layout.fillHeight: true
Layout.minimumHeight: 200
Layout.fillWidth: true
ListView {
id: memberList
clip: true
spacing: 8
boundsBehavior: Flickable.StopAtBounds
model: members
ScrollHelper {
flickable: parent
anchors.fill: parent
enabled: !Settings.mobileMode
}
delegate: RowLayout {
spacing: 10
Avatar {
width: avatarSize
height: avatarSize
userid: model.mxid
url: model.avatarUrl.replace("mxc://", "image://MxcImage/")
displayName: model.displayName
onClicked: TimelineManager.timeline.openUserProfile(model.mxid)
}
ColumnLayout {
spacing: 5
Label {
text: model.displayName
color: TimelineManager.userColor(model ? model.mxid : "", colors.window)
font.pointSize: 12
}
Label {
text: model.mxid
color: colors.buttonText
font.pointSize: 10
}
Item {
Layout.fillHeight: true
Layout.fillWidth: true
}
}
}
}
}
}
footer: DialogButtonBox {
standardButtons: DialogButtonBox.Ok
onAccepted: roomMembersRoot.close()
}
}

View file

@ -116,7 +116,7 @@ Rectangle {
Platform.MenuItem { Platform.MenuItem {
text: qsTr("Members") text: qsTr("Members")
onTriggered: TimelineManager.openMemberListDialog(room.roomId()) onTriggered: Rooms.currentRoom.openRoomMembers(room.roomId())
} }
Platform.MenuItem { Platform.MenuItem {

View file

@ -185,6 +185,7 @@
<file>qml/components/AdaptiveLayout.qml</file> <file>qml/components/AdaptiveLayout.qml</file>
<file>qml/components/AdaptiveLayoutElement.qml</file> <file>qml/components/AdaptiveLayoutElement.qml</file>
<file>qml/components/FlatButton.qml</file> <file>qml/components/FlatButton.qml</file>
<file>qml/RoomMembers.qml</file>
</qresource> </qresource>
<qresource prefix="/media"> <qresource prefix="/media">
<file>media/ring.ogg</file> <file>media/ring.ogg</file>

View file

@ -21,6 +21,7 @@
#include "LoginPage.h" #include "LoginPage.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "MemberList.h"
#include "RegisterPage.h" #include "RegisterPage.h"
#include "TrayIcon.h" #include "TrayIcon.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
@ -36,7 +37,6 @@
#include "dialogs/JoinRoom.h" #include "dialogs/JoinRoom.h"
#include "dialogs/LeaveRoom.h" #include "dialogs/LeaveRoom.h"
#include "dialogs/Logout.h" #include "dialogs/Logout.h"
#include "dialogs/MemberList.h"
#include "dialogs/ReadReceipts.h" #include "dialogs/ReadReceipts.h"
MainWindow *MainWindow::instance_ = nullptr; MainWindow *MainWindow::instance_ = nullptr;
@ -310,14 +310,6 @@ MainWindow::hasActiveUser()
settings.contains(prefix + "auth/user_id"); settings.contains(prefix + "auth/user_id");
} }
void
MainWindow::openMemberListDialog(const QString &room_id)
{
auto dialog = new dialogs::MemberList(room_id, this);
showDialog(dialog);
}
void void
MainWindow::openLeaveRoomDialog(const QString &room_id) MainWindow::openLeaveRoomDialog(const QString &room_id)
{ {

View file

@ -65,7 +65,6 @@ public:
std::function<void(const mtx::requests::CreateRoom &request)> callback); std::function<void(const mtx::requests::CreateRoom &request)> callback);
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback); void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
void openLogoutDialog(); void openLogoutDialog();
void openMemberListDialog(const QString &room_id);
void openReadReceiptsDialog(const QString &event_id); void openReadReceiptsDialog(const QString &event_id);
void hideOverlay(); void hideOverlay();

91
src/MemberList.cpp Normal file
View file

@ -0,0 +1,91 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QAbstractSlider>
#include <QLabel>
#include <QListWidgetItem>
#include <QPainter>
#include <QPushButton>
#include <QScrollBar>
#include <QShortcut>
#include <QStyleOption>
#include <QVBoxLayout>
#include "MemberList.h"
#include "Cache.h"
#include "ChatPage.h"
#include "Config.h"
#include "Logging.h"
#include "Utils.h"
#include "timeline/TimelineViewManager.h"
#include "ui/Avatar.h"
MemberList::MemberList(const QString &room_id, QWidget *parent)
: QAbstractListModel{parent}
, room_id_{room_id}
{
try {
info_ = cache::singleRoomInfo(room_id_.toStdString());
} catch (const lmdb::error &) {
nhlog::db()->warn("failed to retrieve room info from cache: {}",
room_id_.toStdString());
}
try {
addUsers(cache::getMembers(room_id_.toStdString()));
} catch (const lmdb::error &e) {
nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what());
}
}
void
MemberList::addUsers(const std::vector<RoomMember> &members)
{
beginInsertRows(QModelIndex{}, m_memberList.count(), m_memberList.count() + members.size() - 1);
for (const auto &member : members)
m_memberList.push_back(
{member,
ChatPage::instance()->timelineManager()->rooms()->currentRoom()->avatarUrl(
member.user_id)});
endInsertRows();
}
QHash<int, QByteArray>
MemberList::roleNames() const
{
return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}};
}
QVariant
MemberList::data(const QModelIndex &index, int role) const
{
if (!index.isValid() || index.row() >= (int)m_memberList.size() || index.row() < 0)
return {};
switch (role) {
case Mxid:
return m_memberList[index.row()].first.user_id;
case DisplayName:
return m_memberList[index.row()].first.display_name;
case AvatarUrl:
return m_memberList[index.row()].second;
default:
return {};
}
}
bool MemberList::canFetchMore(const QModelIndex &) const
{
const size_t numMembers = rowCount();
return (numMembers > 1 && numMembers < info_.member_count);
}
void
MemberList::fetchMore(const QModelIndex &)
{
addUsers(cache::getMembers(room_id_.toStdString(), rowCount()));
}

58
src/MemberList.h Normal file
View file

@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "CacheStructs.h"
#include <QAbstractListModel>
class MemberList : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
Q_PROPERTY(size_t memberCount READ memberCount NOTIFY memberCountChanged)
Q_PROPERTY(QString avatarUrl READ avatarUrl NOTIFY avatarUrlChanged)
Q_PROPERTY(QString roomId READ roomId NOTIFY roomIdChanged)
public:
enum Roles
{
Mxid,
DisplayName,
AvatarUrl,
};
MemberList(const QString &room_id, QWidget *parent = nullptr);
QHash<int, QByteArray> roleNames() const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
Q_UNUSED(parent)
return static_cast<int>(m_memberList.size());
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QString roomName() const { return QString::fromStdString(info_.name); }
size_t memberCount() const { return info_.member_count; }
QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); }
QString roomId() const { return room_id_; }
signals:
void roomNameChanged();
void memberCountChanged();
void avatarUrlChanged();
void roomIdChanged();
public slots:
void addUsers(const std::vector<RoomMember> &users);
protected:
bool canFetchMore(const QModelIndex &) const;
void fetchMore(const QModelIndex &);
private:
QVector<QPair<RoomMember, QString>> m_memberList;
QString room_id_;
RoomInfo info_;
};

View file

@ -1,146 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include <QAbstractSlider>
#include <QLabel>
#include <QListWidgetItem>
#include <QPainter>
#include <QPushButton>
#include <QScrollBar>
#include <QShortcut>
#include <QStyleOption>
#include <QVBoxLayout>
#include "dialogs/MemberList.h"
#include "Cache.h"
#include "ChatPage.h"
#include "Config.h"
#include "Logging.h"
#include "Utils.h"
#include "ui/Avatar.h"
using namespace dialogs;
MemberItem::MemberItem(const RoomMember &member, QWidget *parent)
: QWidget(parent)
{
topLayout_ = new QHBoxLayout(this);
topLayout_->setMargin(0);
textLayout_ = new QVBoxLayout;
textLayout_->setMargin(0);
textLayout_->setSpacing(0);
avatar_ = new Avatar(this, 44);
avatar_->setLetter(utils::firstChar(member.display_name));
avatar_->setImage(ChatPage::instance()->currentRoom(), member.user_id);
QFont nameFont;
nameFont.setPointSizeF(nameFont.pointSizeF() * 1.1);
userId_ = new QLabel(member.user_id, this);
userName_ = new QLabel(member.display_name, this);
userName_->setFont(nameFont);
textLayout_->addWidget(userName_);
textLayout_->addWidget(userId_);
topLayout_->addWidget(avatar_);
topLayout_->addLayout(textLayout_, 1);
}
void
MemberItem::paintEvent(QPaintEvent *)
{
QStyleOption opt;
opt.init(this);
QPainter p(this);
style()->drawPrimitive(QStyle::PE_Widget, &opt, &p, this);
}
MemberList::MemberList(const QString &room_id, QWidget *parent)
: QFrame(parent)
, room_id_{room_id}
{
setAutoFillBackground(true);
setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
setWindowModality(Qt::WindowModal);
setAttribute(Qt::WA_DeleteOnClose, true);
auto layout = new QVBoxLayout(this);
layout->setSpacing(conf::modals::WIDGET_SPACING);
layout->setMargin(conf::modals::WIDGET_MARGIN);
list_ = new QListWidget;
list_->setFrameStyle(QFrame::NoFrame);
list_->setSelectionMode(QAbstractItemView::NoSelection);
list_->setSpacing(5);
QFont largeFont;
largeFont.setPointSizeF(largeFont.pointSizeF() * 1.5);
setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
setMinimumHeight(list_->sizeHint().height() * 2);
setMinimumWidth(std::max(list_->sizeHint().width() + 4 * conf::modals::WIDGET_MARGIN,
QFontMetrics(largeFont).averageCharWidth() * 30 -
2 * conf::modals::WIDGET_MARGIN));
QFont font;
font.setPointSizeF(font.pointSizeF() * conf::modals::LABEL_MEDIUM_SIZE_RATIO);
topLabel_ = new QLabel(tr("Room members"), this);
topLabel_->setAlignment(Qt::AlignCenter);
topLabel_->setFont(font);
auto okBtn = new QPushButton(tr("OK"), this);
auto buttonLayout = new QHBoxLayout();
buttonLayout->setSpacing(15);
buttonLayout->addStretch(1);
buttonLayout->addWidget(okBtn);
layout->addWidget(topLabel_);
layout->addWidget(list_);
layout->addLayout(buttonLayout);
list_->clear();
connect(list_->verticalScrollBar(), &QAbstractSlider::valueChanged, this, [this](int pos) {
if (pos != list_->verticalScrollBar()->maximum())
return;
const size_t numMembers = list_->count() - 1;
if (numMembers > 0)
addUsers(cache::getMembers(room_id_.toStdString(), numMembers));
});
try {
addUsers(cache::getMembers(room_id_.toStdString()));
} catch (const lmdb::error &e) {
nhlog::db()->critical("Failed to retrieve members from cache: {}", e.what());
}
auto closeShortcut = new QShortcut(QKeySequence(QKeySequence::Cancel), this);
connect(closeShortcut, &QShortcut::activated, this, &MemberList::close);
connect(okBtn, &QPushButton::clicked, this, &MemberList::close);
}
void
MemberList::addUsers(const std::vector<RoomMember> &members)
{
for (const auto &member : members) {
auto user = new MemberItem(member, this);
auto item = new QListWidgetItem;
item->setSizeHint(user->minimumSizeHint());
item->setFlags(Qt::NoItemFlags);
item->setTextAlignment(Qt::AlignCenter);
list_->insertItem(list_->count() - 1, item);
list_->setItemWidget(item, user);
}
}

View file

@ -1,57 +0,0 @@
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QFrame>
#include <QListWidget>
class Avatar;
class QPushButton;
class QHBoxLayout;
class QLabel;
class QVBoxLayout;
struct RoomMember;
template<class T>
class QSharedPointer;
namespace dialogs {
class MemberItem : public QWidget
{
Q_OBJECT
public:
MemberItem(const RoomMember &member, QWidget *parent);
protected:
void paintEvent(QPaintEvent *) override;
private:
QHBoxLayout *topLayout_;
QVBoxLayout *textLayout_;
Avatar *avatar_;
QLabel *userName_;
QLabel *userId_;
};
class MemberList : public QFrame
{
Q_OBJECT
public:
MemberList(const QString &room_id, QWidget *parent = nullptr);
public slots:
void addUsers(const std::vector<RoomMember> &users);
private:
QString room_id_;
QLabel *topLabel_;
QListWidget *list_;
};
} // dialogs

View file

@ -205,6 +205,9 @@ main(int argc, char *argv[])
parser.process(app); parser.process(app);
// make sure that size_t properties will work
qRegisterMetaType<size_t>("size_t");
app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"})); app.setWindowIcon(QIcon::fromTheme("nheko", QIcon{":/logos/nheko.png"}));
http::init(); http::init();

View file

@ -25,6 +25,7 @@
#include "Logging.h" #include "Logging.h"
#include "MainWindow.h" #include "MainWindow.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "MemberList.h"
#include "MxcImageProvider.h" #include "MxcImageProvider.h"
#include "Olm.h" #include "Olm.h"
#include "TimelineViewManager.h" #include "TimelineViewManager.h"
@ -1061,9 +1062,16 @@ TimelineModel::openUserProfile(QString userid)
} }
void void
TimelineModel::openRoomSettings() TimelineModel::openRoomMembers()
{ {
RoomSettings *settings = new RoomSettings(roomId(), this); MemberList *memberList = new MemberList(roomId());
emit openRoomMembersDialog(memberList);
}
void
TimelineModel::openRoomSettings(QString room_id)
{
RoomSettings *settings = new RoomSettings(room_id == QString() ? roomId() : room_id, this);
connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged); connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged);
openRoomSettingsDialog(settings); openRoomSettingsDialog(settings);
} }

View file

@ -17,6 +17,7 @@
#include "CacheStructs.h" #include "CacheStructs.h"
#include "EventStore.h" #include "EventStore.h"
#include "InputBar.h" #include "InputBar.h"
#include "MemberList.h"
#include "Permissions.h" #include "Permissions.h"
#include "ui/RoomSettings.h" #include "ui/RoomSettings.h"
#include "ui/UserProfile.h" #include "ui/UserProfile.h"
@ -235,7 +236,8 @@ public:
Q_INVOKABLE void forwardMessage(QString eventId, QString roomId); Q_INVOKABLE void forwardMessage(QString eventId, QString roomId);
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const; Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
Q_INVOKABLE void openUserProfile(QString userid); Q_INVOKABLE void openUserProfile(QString userid);
Q_INVOKABLE void openRoomSettings(); Q_INVOKABLE void openRoomMembers();
Q_INVOKABLE void openRoomSettings(QString room_id = QString());
Q_INVOKABLE void editAction(QString id); Q_INVOKABLE void editAction(QString id);
Q_INVOKABLE void replyAction(QString id); Q_INVOKABLE void replyAction(QString id);
Q_INVOKABLE void readReceiptsAction(QString id) const; Q_INVOKABLE void readReceiptsAction(QString id) const;
@ -352,6 +354,7 @@ signals:
void lastMessageChanged(); void lastMessageChanged();
void notificationsChanged(); void notificationsChanged();
void openRoomMembersDialog(MemberList *members);
void openRoomSettingsDialog(RoomSettings *settings); void openRoomSettingsDialog(RoomSettings *settings);
void newMessageToSend(mtx::events::collections::TimelineEvents event); void newMessageToSend(mtx::events::collections::TimelineEvents event);

View file

@ -174,6 +174,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
0, 0,
"UserProfileModel", "UserProfileModel",
"UserProfile needs to be instantiated on the C++ side"); "UserProfile needs to be instantiated on the C++ side");
qmlRegisterUncreatableType<MemberList>(
"im.nheko", 1, 0, "MemberList", "MemberList needs to be instantiated on the C++ side");
qmlRegisterUncreatableType<RoomSettings>( qmlRegisterUncreatableType<RoomSettings>(
"im.nheko", "im.nheko",
1, 1,
@ -428,11 +430,6 @@ TimelineViewManager::openInviteUsersDialog()
[this](const QStringList &invitees) { emit inviteUsers(invitees); }); [this](const QStringList &invitees) { emit inviteUsers(invitees); });
} }
void void
TimelineViewManager::openMemberListDialog(QString roomid) const
{
MainWindow::instance()->openMemberListDialog(roomid);
}
void
TimelineViewManager::openLeaveRoomDialog(QString roomid) const TimelineViewManager::openLeaveRoomDialog(QString roomid) const
{ {
MainWindow::instance()->openLeaveRoomDialog(roomid); MainWindow::instance()->openLeaveRoomDialog(roomid);

View file

@ -66,7 +66,6 @@ public:
Q_INVOKABLE void focusMessageInput(); Q_INVOKABLE void focusMessageInput();
Q_INVOKABLE void openInviteUsersDialog(); Q_INVOKABLE void openInviteUsersDialog();
Q_INVOKABLE void openMemberListDialog(QString roomid) const;
Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const; Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const;
Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow); Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);