diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 10a91557..6b8c1e10 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -116,29 +116,31 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
connect(this, &ChatPage::loggedOut, this, &ChatPage::logout);
- connect(view_manager_, &TimelineViewManager::inviteUsers, this, [this](QStringList users) {
- const auto room_id = currentRoom().toStdString();
-
- for (int ii = 0; ii < users.size(); ++ii) {
- QTimer::singleShot(ii * 500, this, [this, room_id, ii, users]() {
- const auto user = users.at(ii);
-
- http::client()->invite_user(
- room_id,
- user.toStdString(),
- [this, user](const mtx::responses::RoomInvite &,
- mtx::http::RequestErr err) {
- if (err) {
- emit showNotification(
- tr("Failed to invite user: %1").arg(user));
- return;
- }
-
- emit showNotification(tr("Invited user: %1").arg(user));
- });
- });
- }
- });
+ connect(
+ view_manager_,
+ &TimelineViewManager::inviteUsers,
+ this,
+ [this](QString roomId, QStringList users) {
+ for (int ii = 0; ii < users.size(); ++ii) {
+ QTimer::singleShot(ii * 500, this, [this, roomId, ii, users]() {
+ const auto user = users.at(ii);
+
+ http::client()->invite_user(
+ roomId.toStdString(),
+ user.toStdString(),
+ [this, user](const mtx::responses::RoomInvite &,
+ mtx::http::RequestErr err) {
+ if (err) {
+ emit showNotification(
+ tr("Failed to invite user: %1").arg(user));
+ return;
+ }
+
+ emit showNotification(tr("Invited user: %1").arg(user));
+ });
+ });
+ }
+ });
connect(this, &ChatPage::leftRoom, this, &ChatPage::removeRoom);
connect(this, &ChatPage::newRoom, this, &ChatPage::changeRoom, Qt::QueuedConnection);
diff --git a/src/InviteeItem.cpp b/src/InviteeItem.cpp
deleted file mode 100644
index 27f02560..00000000
--- a/src/InviteeItem.cpp
+++ /dev/null
@@ -1,28 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include <QHBoxLayout>
-#include <QLabel>
-#include <QPushButton>
-
-#include "InviteeItem.h"
-
-constexpr int SidePadding = 10;
-
-InviteeItem::InviteeItem(mtx::identifiers::User user, QWidget *parent)
- : QWidget{parent}
- , user_{QString::fromStdString(user.to_string())}
-{
- auto topLayout_ = new QHBoxLayout(this);
- topLayout_->setSpacing(0);
- topLayout_->setContentsMargins(SidePadding, 0, 3 * SidePadding, 0);
-
- name_ = new QLabel(user_, this);
- removeUserBtn_ = new QPushButton(tr("Remove"), this);
-
- topLayout_->addWidget(name_);
- topLayout_->addWidget(removeUserBtn_, 0, Qt::AlignRight);
-
- connect(removeUserBtn_, &QPushButton::clicked, this, &InviteeItem::removeItem);
-}
diff --git a/src/InviteeItem.h b/src/InviteeItem.h
deleted file mode 100644
index 014541ea..00000000
--- a/src/InviteeItem.h
+++ /dev/null
@@ -1,31 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include <QWidget>
-
-#include <mtx/identifiers.hpp>
-
-class QPushButton;
-class QLabel;
-
-class InviteeItem : public QWidget
-{
- Q_OBJECT
-
-public:
- InviteeItem(mtx::identifiers::User user, QWidget *parent = nullptr);
-
- QString userID() { return user_; }
-
-signals:
- void removeItem();
-
-private:
- QString user_;
-
- QLabel *name_;
- QPushButton *removeUserBtn_;
-};
diff --git a/src/InviteesModel.cpp b/src/InviteesModel.cpp
new file mode 100644
index 00000000..27b2116f
--- /dev/null
+++ b/src/InviteesModel.cpp
@@ -0,0 +1,84 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#include "InviteesModel.h"
+
+#include "Cache.h"
+#include "Logging.h"
+#include "MatrixClient.h"
+#include "mtx/responses/profile.hpp"
+
+InviteesModel::InviteesModel(QObject *parent)
+ : QAbstractListModel{parent}
+{}
+
+void
+InviteesModel::addUser(QString mxid)
+{
+ beginInsertRows(QModelIndex(), invitees_.count(), invitees_.count());
+
+ auto invitee = new Invitee{mxid, this};
+ auto indexOfInvitee = invitees_.count();
+ connect(invitee, &Invitee::userInfoLoaded, this, [this, indexOfInvitee]() {
+ emit dataChanged(index(indexOfInvitee), index(indexOfInvitee));
+ });
+
+ invitees_.push_back(invitee);
+
+ endInsertRows();
+ emit countChanged();
+}
+
+QHash<int, QByteArray>
+InviteesModel::roleNames() const
+{
+ return {{Mxid, "mxid"}, {DisplayName, "displayName"}, {AvatarUrl, "avatarUrl"}};
+}
+
+QVariant
+InviteesModel::data(const QModelIndex &index, int role) const
+{
+ if (!index.isValid() || index.row() >= (int)invitees_.size() || index.row() < 0)
+ return {};
+
+ switch (role) {
+ case Mxid:
+ return invitees_[index.row()]->mxid_;
+ case DisplayName:
+ return invitees_[index.row()]->displayName_;
+ case AvatarUrl:
+ return invitees_[index.row()]->avatarUrl_;
+ default:
+ return {};
+ }
+}
+
+QStringList
+InviteesModel::mxids()
+{
+ QStringList mxidList;
+ for (int i = 0; i < invitees_.length(); ++i)
+ mxidList.push_back(invitees_[i]->mxid_);
+ return mxidList;
+}
+
+Invitee::Invitee(const QString &mxid, QObject *parent)
+ : QObject{parent}
+ , mxid_{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");
+ emit userInfoLoaded();
+ return;
+ }
+
+ displayName_ = QString::fromStdString(res.display_name);
+ avatarUrl_ = QString::fromStdString(res.avatar_url);
+
+ emit userInfoLoaded();
+ });
+}
diff --git a/src/InviteesModel.h b/src/InviteesModel.h
new file mode 100644
index 00000000..a4e19ebb
--- /dev/null
+++ b/src/InviteesModel.h
@@ -0,0 +1,63 @@
+// SPDX-FileCopyrightText: 2021 Nheko Contributors
+//
+// SPDX-License-Identifier: GPL-3.0-or-later
+
+#ifndef INVITEESMODEL_H
+#define INVITEESMODEL_H
+
+#include <QAbstractListModel>
+#include <QVector>
+
+class Invitee : public QObject
+{
+ Q_OBJECT
+
+public:
+ Invitee(const QString &mxid, QObject *parent = nullptr);
+
+signals:
+ void userInfoLoaded();
+
+private:
+ const QString mxid_;
+ QString displayName_;
+ QString avatarUrl_;
+
+ friend class InviteesModel;
+};
+
+class InviteesModel : public QAbstractListModel
+{
+ Q_OBJECT
+
+ Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
+
+public:
+ enum Roles
+ {
+ Mxid,
+ DisplayName,
+ AvatarUrl,
+ };
+
+ InviteesModel(QObject *parent = nullptr);
+
+ Q_INVOKABLE void addUser(QString mxid);
+
+ QHash<int, QByteArray> roleNames() const override;
+ int rowCount(const QModelIndex & = QModelIndex()) const override
+ {
+ return (int)invitees_.size();
+ }
+ QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
+ QStringList mxids();
+
+signals:
+ void accept();
+ void countChanged();
+
+private:
+ QVector<Invitee *> invitees_;
+};
+
+#endif // INVITEESMODEL_H
diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
index ed337ca4..c0486d01 100644
--- a/src/MainWindow.cpp
+++ b/src/MainWindow.cpp
@@ -21,6 +21,7 @@
#include "LoginPage.h"
#include "MainWindow.h"
#include "MatrixClient.h"
+#include "MemberList.h"
#include "RegisterPage.h"
#include "TrayIcon.h"
#include "UserSettingsPage.h"
@@ -32,11 +33,9 @@
#include "ui/SnackBar.h"
#include "dialogs/CreateRoom.h"
-#include "dialogs/InviteUsers.h"
#include "dialogs/JoinRoom.h"
#include "dialogs/LeaveRoom.h"
#include "dialogs/Logout.h"
-#include "dialogs/MemberList.h"
#include "dialogs/ReadReceipts.h"
MainWindow *MainWindow::instance_ = nullptr;
@@ -311,14 +310,6 @@ MainWindow::hasActiveUser()
}
void
-MainWindow::openMemberListDialog(const QString &room_id)
-{
- auto dialog = new dialogs::MemberList(room_id, this);
-
- showDialog(dialog);
-}
-
-void
MainWindow::openLeaveRoomDialog(const QString &room_id)
{
auto dialog = new dialogs::LeaveRoom(this);
@@ -342,18 +333,6 @@ MainWindow::showOverlayProgressBar()
}
void
-MainWindow::openInviteUsersDialog(std::function<void(const QStringList &invitees)> callback)
-{
- auto dialog = new dialogs::InviteUsers(this);
- connect(dialog, &dialogs::InviteUsers::sendInvites, this, [callback](QStringList invitees) {
- if (!invitees.isEmpty())
- callback(invitees);
- });
-
- showDialog(dialog);
-}
-
-void
MainWindow::openJoinRoomDialog(std::function<void(const QString &room_id)> callback)
{
auto dialog = new dialogs::JoinRoom(this);
diff --git a/src/MainWindow.h b/src/MainWindow.h
index 3571f079..6d62545c 100644
--- a/src/MainWindow.h
+++ b/src/MainWindow.h
@@ -65,7 +65,6 @@ public:
std::function<void(const mtx::requests::CreateRoom &request)> callback);
void openJoinRoomDialog(std::function<void(const QString &room_id)> callback);
void openLogoutDialog();
- void openMemberListDialog(const QString &room_id);
void openReadReceiptsDialog(const QString &event_id);
void hideOverlay();
diff --git a/src/MemberList.cpp b/src/MemberList.cpp
new file mode 100644
index 00000000..415e3b57
--- /dev/null
+++ b/src/MemberList.cpp
@@ -0,0 +1,111 @@
+// 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 {
+ auto members = cache::getMembers(room_id_.toStdString());
+ addUsers(members);
+ numUsersLoaded_ = members.size();
+ } 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();
+ if (numMembers > 1 && numMembers < info_.member_count)
+ return true;
+ else
+ return false;
+}
+
+void
+MemberList::fetchMore(const QModelIndex &)
+{
+ loadingMoreMembers_ = true;
+ emit loadingMoreMembersChanged();
+
+ auto members = cache::getMembers(room_id_.toStdString(), rowCount());
+ addUsers(members);
+ numUsersLoaded_ += members.size();
+ emit numUsersLoadedChanged();
+
+ loadingMoreMembers_ = false;
+ emit loadingMoreMembersChanged();
+}
diff --git a/src/MemberList.h b/src/MemberList.h
new file mode 100644
index 00000000..070666a2
--- /dev/null
+++ b/src/MemberList.h
@@ -0,0 +1,66 @@
+// 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(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 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); }
+ int memberCount() const { return info_.member_count; }
+ QString avatarUrl() const { return QString::fromStdString(info_.avatar_url); }
+ QString roomId() const { return room_id_; }
+ int numUsersLoaded() const { return numUsersLoaded_; }
+ bool loadingMoreMembers() const { return loadingMoreMembers_; }
+
+signals:
+ void roomNameChanged();
+ void memberCountChanged();
+ void avatarUrlChanged();
+ void roomIdChanged();
+ void numUsersLoadedChanged();
+ void loadingMoreMembersChanged();
+
+public slots:
+ void addUsers(const std::vector<RoomMember> &users);
+
+protected:
+ bool canFetchMore(const QModelIndex &) const override;
+ void fetchMore(const QModelIndex &) override;
+
+private:
+ QVector<QPair<RoomMember, QString>> m_memberList;
+ QString room_id_;
+ RoomInfo info_;
+ int numUsersLoaded_{0};
+ bool loadingMoreMembers_{false};
+};
diff --git a/src/dialogs/InviteUsers.cpp b/src/dialogs/InviteUsers.cpp
deleted file mode 100644
index 9dd6085f..00000000
--- a/src/dialogs/InviteUsers.cpp
+++ /dev/null
@@ -1,158 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#include <QDebug>
-#include <QIcon>
-#include <QLabel>
-#include <QListWidget>
-#include <QListWidgetItem>
-#include <QPushButton>
-#include <QStyleOption>
-#include <QTimer>
-#include <QVBoxLayout>
-
-#include "dialogs/InviteUsers.h"
-
-#include "Config.h"
-#include "InviteeItem.h"
-#include "ui/TextField.h"
-
-#include <mtx/identifiers.hpp>
-
-using namespace dialogs;
-
-InviteUsers::InviteUsers(QWidget *parent)
- : QFrame(parent)
-{
- setAutoFillBackground(true);
- setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
- setWindowModality(Qt::WindowModal);
- setAttribute(Qt::WA_DeleteOnClose, true);
-
- setMinimumWidth(conf::window::minModalWidth);
- setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
-
- auto layout = new QVBoxLayout(this);
- layout->setSpacing(conf::modals::WIDGET_SPACING);
- layout->setMargin(conf::modals::WIDGET_MARGIN);
-
- auto buttonLayout = new QHBoxLayout();
- buttonLayout->setSpacing(0);
- buttonLayout->setMargin(0);
-
- confirmBtn_ = new QPushButton("Invite", this);
- confirmBtn_->setDefault(true);
- cancelBtn_ = new QPushButton(tr("Cancel"), this);
-
- buttonLayout->addStretch(1);
- buttonLayout->setSpacing(15);
- buttonLayout->addWidget(cancelBtn_);
- buttonLayout->addWidget(confirmBtn_);
-
- inviteeInput_ = new TextField(this);
- inviteeInput_->setLabel(tr("User ID to invite"));
-
- inviteeList_ = new QListWidget;
- inviteeList_->setFrameStyle(QFrame::NoFrame);
- inviteeList_->setSelectionMode(QAbstractItemView::NoSelection);
- inviteeList_->setAttribute(Qt::WA_MacShowFocusRect, 0);
- inviteeList_->setSpacing(5);
-
- errorLabel_ = new QLabel(this);
- errorLabel_->setAlignment(Qt::AlignCenter);
-
- layout->addWidget(inviteeInput_);
- layout->addWidget(errorLabel_);
- layout->addWidget(inviteeList_);
- layout->addLayout(buttonLayout);
-
- connect(inviteeInput_, &TextField::returnPressed, this, &InviteUsers::addUser);
- connect(confirmBtn_, &QPushButton::clicked, [this]() {
- if (!inviteeInput_->text().trimmed().isEmpty()) {
- addUser();
- }
-
- emit sendInvites(invitedUsers());
-
- inviteeInput_->clear();
- inviteeList_->clear();
- errorLabel_->hide();
-
- emit close();
- });
-
- connect(cancelBtn_, &QPushButton::clicked, [this]() {
- inviteeInput_->clear();
- inviteeList_->clear();
- errorLabel_->hide();
-
- emit close();
- });
-}
-
-void
-InviteUsers::addUser()
-{
- auto user_id = inviteeInput_->text();
-
- try {
- namespace ids = mtx::identifiers;
- auto user = ids::parse<ids::User>(user_id.toStdString());
-
- auto item = new QListWidgetItem(inviteeList_);
- auto invitee = new InviteeItem(user, this);
-
- item->setSizeHint(invitee->minimumSizeHint());
- item->setFlags(Qt::NoItemFlags);
- item->setTextAlignment(Qt::AlignCenter);
-
- inviteeList_->setItemWidget(item, invitee);
-
- connect(invitee, &InviteeItem::removeItem, this, [this, item]() {
- emit removeInvitee(item);
- });
-
- errorLabel_->hide();
- inviteeInput_->clear();
- } catch (std::exception &e) {
- errorLabel_->setText(e.what());
- errorLabel_->show();
- }
-}
-
-void
-InviteUsers::removeInvitee(QListWidgetItem *item)
-{
- int row = inviteeList_->row(item);
- auto widget = inviteeList_->takeItem(row);
-
- inviteeList_->removeItemWidget(widget);
-}
-
-QStringList
-InviteUsers::invitedUsers() const
-{
- QStringList users;
-
- for (int ii = 0; ii < inviteeList_->count(); ++ii) {
- auto item = inviteeList_->item(ii);
- auto widget = inviteeList_->itemWidget(item);
- auto invitee = qobject_cast<InviteeItem *>(widget);
-
- if (invitee)
- users << invitee->userID();
- else
- qDebug() << "Cast InviteeItem failed";
- }
-
- return users;
-}
-
-void
-InviteUsers::showEvent(QShowEvent *event)
-{
- inviteeInput_->setFocus();
-
- QFrame::showEvent(event);
-}
diff --git a/src/dialogs/InviteUsers.h b/src/dialogs/InviteUsers.h
deleted file mode 100644
index e40183c1..00000000
--- a/src/dialogs/InviteUsers.h
+++ /dev/null
@@ -1,45 +0,0 @@
-// SPDX-FileCopyrightText: 2021 Nheko Contributors
-//
-// SPDX-License-Identifier: GPL-3.0-or-later
-
-#pragma once
-
-#include <QFrame>
-#include <QStringList>
-
-class QPushButton;
-class QLabel;
-class TextField;
-class QListWidget;
-class QListWidgetItem;
-
-namespace dialogs {
-
-class InviteUsers : public QFrame
-{
- Q_OBJECT
-public:
- explicit InviteUsers(QWidget *parent = nullptr);
-
-protected:
- void showEvent(QShowEvent *event) override;
-
-signals:
- void sendInvites(QStringList invitees);
-
-private slots:
- void removeInvitee(QListWidgetItem *item);
-
-private:
- void addUser();
- QStringList invitedUsers() const;
-
- QPushButton *confirmBtn_;
- QPushButton *cancelBtn_;
-
- TextField *inviteeInput_;
- QLabel *errorLabel_;
-
- QListWidget *inviteeList_;
-};
-} // dialogs
diff --git a/src/dialogs/MemberList.cpp b/src/dialogs/MemberList.cpp
deleted file mode 100644
index 21eb72b0..00000000
--- a/src/dialogs/MemberList.cpp
+++ /dev/null
@@ -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);
- }
-}
diff --git a/src/dialogs/MemberList.h b/src/dialogs/MemberList.h
deleted file mode 100644
index b822eec8..00000000
--- a/src/dialogs/MemberList.h
+++ /dev/null
@@ -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
diff --git a/src/timeline/Permissions.cpp b/src/timeline/Permissions.cpp
index 1eaab468..e4957045 100644
--- a/src/timeline/Permissions.cpp
+++ b/src/timeline/Permissions.cpp
@@ -8,9 +8,9 @@
#include "MatrixClient.h"
#include "TimelineModel.h"
-Permissions::Permissions(TimelineModel *parent)
+Permissions::Permissions(QString roomId, QObject *parent)
: QObject(parent)
- , room(parent)
+ , roomId_(roomId)
{
invalidate();
}
@@ -19,7 +19,7 @@ void
Permissions::invalidate()
{
pl = cache::client()
- ->getStateEvent<mtx::events::state::PowerLevels>(room->roomId().toStdString())
+ ->getStateEvent<mtx::events::state::PowerLevels>(roomId_.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::PowerLevels>{})
.content;
}
diff --git a/src/timeline/Permissions.h b/src/timeline/Permissions.h
index f7e6f389..7aab1ddb 100644
--- a/src/timeline/Permissions.h
+++ b/src/timeline/Permissions.h
@@ -15,7 +15,7 @@ class Permissions : public QObject
Q_OBJECT
public:
- Permissions(TimelineModel *parent);
+ Permissions(QString roomId, QObject *parent = nullptr);
Q_INVOKABLE bool canInvite();
Q_INVOKABLE bool canBan();
@@ -28,6 +28,6 @@ public:
void invalidate();
private:
- TimelineModel *room;
+ QString roomId_;
mtx::events::state::PowerLevels pl;
};
diff --git a/src/timeline/TimelineModel.cpp b/src/timeline/TimelineModel.cpp
index 7b3f0729..66d931fd 100644
--- a/src/timeline/TimelineModel.cpp
+++ b/src/timeline/TimelineModel.cpp
@@ -25,6 +25,7 @@
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
+#include "MemberList.h"
#include "MxcImageProvider.h"
#include "Olm.h"
#include "TimelineViewManager.h"
@@ -317,6 +318,7 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
, events(room_id.toStdString(), this)
, room_id_(room_id)
, manager_(manager)
+ , permissions_{room_id}
{
lastMessage_.timestamp = 0;
@@ -325,6 +327,10 @@ TimelineModel::TimelineModel(TimelineViewManager *manager, QString room_id, QObj
this->isSpace_ = create->content.type == mtx::events::state::room_type::space;
this->isEncrypted_ = cache::isRoomEncrypted(room_id_.toStdString());
+ // this connection will simplify adding the plainRoomNameChanged() signal everywhere that it
+ // needs to be
+ connect(this, &TimelineModel::roomNameChanged, this, &TimelineModel::plainRoomNameChanged);
+
connect(
this,
&TimelineModel::redactionFailed,
@@ -1061,11 +1067,28 @@ TimelineModel::openUserProfile(QString userid)
}
void
-TimelineModel::openRoomSettings()
+TimelineModel::openRoomMembers()
+{
+ MemberList *memberList = new MemberList(roomId());
+ emit openRoomMembersDialog(memberList);
+}
+
+void
+TimelineModel::openRoomSettings(QString room_id)
{
- RoomSettings *settings = new RoomSettings(roomId(), this);
+ RoomSettings *settings = new RoomSettings(room_id == QString() ? roomId() : room_id, this);
connect(this, &TimelineModel::roomAvatarUrlChanged, settings, &RoomSettings::avatarChanged);
- openRoomSettingsDialog(settings);
+ emit openRoomSettingsDialog(settings);
+}
+
+void
+TimelineModel::openInviteUsers(QString roomId)
+{
+ InviteesModel *model = new InviteesModel{this};
+ connect(model, &InviteesModel::accept, this, [this, model, roomId]() {
+ emit manager_->inviteUsers(roomId == QString() ? room_id_ : roomId, model->mxids());
+ });
+ emit openInviteUsersDialog(model);
}
void
diff --git a/src/timeline/TimelineModel.h b/src/timeline/TimelineModel.h
index 3c80ade8..0d1eb1f9 100644
--- a/src/timeline/TimelineModel.h
+++ b/src/timeline/TimelineModel.h
@@ -17,6 +17,8 @@
#include "CacheStructs.h"
#include "EventStore.h"
#include "InputBar.h"
+#include "InviteesModel.h"
+#include "MemberList.h"
#include "Permissions.h"
#include "ui/RoomSettings.h"
#include "ui/UserProfile.h"
@@ -158,7 +160,9 @@ class TimelineModel : public QAbstractListModel
Q_PROPERTY(QString edit READ edit WRITE setEdit NOTIFY editChanged RESET resetEdit)
Q_PROPERTY(
bool paginationInProgress READ paginationInProgress NOTIFY paginationInProgressChanged)
+ Q_PROPERTY(QString roomId READ roomId CONSTANT)
Q_PROPERTY(QString roomName READ roomName NOTIFY roomNameChanged)
+ Q_PROPERTY(QString plainRoomName READ plainRoomName NOTIFY plainRoomNameChanged)
Q_PROPERTY(QString roomAvatarUrl READ roomAvatarUrl NOTIFY roomAvatarUrlChanged)
Q_PROPERTY(QString roomTopic READ roomTopic NOTIFY roomTopicChanged)
Q_PROPERTY(int roomMemberCount READ roomMemberCount NOTIFY roomMemberCountChanged)
@@ -235,7 +239,9 @@ public:
Q_INVOKABLE void forwardMessage(QString eventId, QString roomId);
Q_INVOKABLE void viewDecryptedRawMessage(QString id) const;
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 openInviteUsers(QString roomId = QString());
Q_INVOKABLE void editAction(QString id);
Q_INVOKABLE void replyAction(QString id);
Q_INVOKABLE void readReceiptsAction(QString id) const;
@@ -352,7 +358,9 @@ signals:
void lastMessageChanged();
void notificationsChanged();
+ void openRoomMembersDialog(MemberList *members);
void openRoomSettingsDialog(RoomSettings *settings);
+ void openInviteUsersDialog(InviteesModel *invitees);
void newMessageToSend(mtx::events::collections::TimelineEvents event);
void addPendingMessageToStore(mtx::events::collections::TimelineEvents event);
@@ -360,6 +368,7 @@ signals:
void encryptionChanged();
void roomNameChanged();
+ void plainRoomNameChanged();
void roomTopicChanged();
void roomAvatarUrlChanged();
void roomMemberCountChanged();
@@ -389,7 +398,7 @@ private:
TimelineViewManager *manager_;
InputBar input_{this};
- Permissions permissions_{this};
+ Permissions permissions_;
QTimer showEventTimer{this};
QString eventIdToShow;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index 3e69f92b..64493e5b 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -20,6 +20,7 @@
#include "DeviceVerificationFlow.h"
#include "EventAccessors.h"
#include "ImagePackModel.h"
+#include "InviteesModel.h"
#include "Logging.h"
#include "MainWindow.h"
#include "MatrixClient.h"
@@ -174,6 +175,8 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
0,
"UserProfileModel",
"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>(
"im.nheko",
1,
@@ -182,6 +185,12 @@ TimelineViewManager::TimelineViewManager(CallManager *callManager, ChatPage *par
"Room Settings needs to be instantiated on the C++ side");
qmlRegisterUncreatableType<TimelineModel>(
"im.nheko", 1, 0, "Room", "Room needs to be instantiated on the C++ side");
+ qmlRegisterUncreatableType<InviteesModel>(
+ "im.nheko",
+ 1,
+ 0,
+ "InviteesModel",
+ "InviteesModel needs to be instantiated on the C++ side");
static auto self = this;
qmlRegisterSingletonType<MainWindow>(
@@ -422,17 +431,6 @@ TimelineViewManager::openImageOverlayInternal(QString eventId, QImage img)
}
void
-TimelineViewManager::openInviteUsersDialog()
-{
- MainWindow::instance()->openInviteUsersDialog(
- [this](const QStringList &invitees) { emit inviteUsers(invitees); });
-}
-void
-TimelineViewManager::openMemberListDialog(QString roomid) const
-{
- MainWindow::instance()->openMemberListDialog(roomid);
-}
-void
TimelineViewManager::openLeaveRoomDialog(QString roomid) const
{
MainWindow::instance()->openLeaveRoomDialog(roomid);
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 15b4f523..945ba2d5 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -65,8 +65,6 @@ public:
Q_INVOKABLE QString userStatus(QString id) const;
Q_INVOKABLE void focusMessageInput();
- Q_INVOKABLE void openInviteUsersDialog();
- Q_INVOKABLE void openMemberListDialog(QString roomid) const;
Q_INVOKABLE void openLeaveRoomDialog(QString roomid) const;
Q_INVOKABLE void removeVerificationFlow(DeviceVerificationFlow *flow);
@@ -81,7 +79,9 @@ signals:
void replyingEventChanged(QString replyingEvent);
void replyClosed();
void newDeviceVerificationRequest(DeviceVerificationFlow *flow);
- void inviteUsers(QStringList users);
+ void inviteUsers(QString roomId, QStringList users);
+ void showRoomList();
+ void narrowViewChanged();
void focusChanged();
void focusInput();
void openImageOverlayInternalCb(QString eventId, QImage img);
|