Update user sort

Update the user model so it also sorts by power level and update how we initialize the model to improve performance.

The following is also changed:
- Store a single `UserListModel` in `RoomManager` and use it for everything, this means we don't create extra models (incluiding the long initialisation for each in big rooms)
- By using the single model once it has loaded the users of the new room opening and closing the draw now happens instantly (previously the model would have to be loaded every time the drawer was opened).
- To stop the initial loading and room change of Neochat slowing down (as the `UserListModel` would be loaded before the `TimelineView` is shown) the initialisation of the model is delayed until the `TimelineView` has loaded. This prioritises showing some messages in the timeline over populating the model so in large rooms the user list will initially be blank, but this keeps the initial load snappier.
This commit is contained in:
James Graham
2024-07-20 18:12:30 +00:00
parent 73de99f661
commit 042032ec46
9 changed files with 69 additions and 31 deletions

View File

@@ -9,18 +9,16 @@
#include "customemojimodel.h"
#include "emojimodel.h"
#include "neochatroom.h"
#include "roommanager.h"
#include "userlistmodel.h"
CompletionModel::CompletionModel(QObject *parent)
: QAbstractListModel(parent)
, m_filterModel(new CompletionProxyModel())
, m_userListModel(new UserListModel(this))
, m_userListModel(RoomManager::instance().userListModel())
, m_emojiModel(new QConcatenateTablesProxyModel(this))
{
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
connect(this, &CompletionModel::roomChanged, this, [this]() {
m_userListModel->setRoom(m_room);
});
m_emojiModel->addSourceModel(&CustomEmojiModel::instance());
m_emojiModel->addSourceModel(&EmojiModel::instance());
}

View File

@@ -33,6 +33,7 @@ void UserListModel::setRoom(NeoChatRoom *room)
m_currentRoom->disconnect(this);
m_currentRoom->connection()->disconnect(this);
m_currentRoom = nullptr;
m_members.clear();
endResetModel();
}
@@ -56,7 +57,7 @@ void UserListModel::setRoom(NeoChatRoom *room)
});
}
refreshAllMembers();
m_active = false;
Q_EMIT roomChanged();
}
@@ -169,7 +170,6 @@ void UserListModel::refreshMember(const Quotient::RoomMember &member, const QLis
void UserListModel::refreshAllMembers()
{
beginResetModel();
m_members.clear();
if (m_currentRoom != nullptr) {
m_members = m_currentRoom->joinedMemberIds();
@@ -179,8 +179,17 @@ void UserListModel::refreshAllMembers()
MemberSorter sorter(m_currentRoom);
#endif
std::sort(m_members.begin(), m_members.end(), [&sorter, this](const auto &left, const auto &right) {
const auto leftPl = m_currentRoom->getUserPowerLevel(left);
const auto rightPl = m_currentRoom->getUserPowerLevel(right);
if (leftPl > rightPl) {
return true;
} else if (rightPl > leftPl) {
return false;
}
return sorter(m_currentRoom->member(left), m_currentRoom->member(right));
});
}
endResetModel();
Q_EMIT usersRefreshed();
@@ -216,4 +225,14 @@ QHash<int, QByteArray> UserListModel::roleNames() const
return roles;
}
void UserListModel::activate()
{
if (m_active) {
return;
}
m_active = true;
refreshAllMembers();
}
#include "moc_userlistmodel.cpp"

View File

@@ -77,6 +77,8 @@ public:
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
void activate();
Q_SIGNALS:
void roomChanged();
void usersRefreshed();
@@ -94,6 +96,8 @@ private:
QPointer<NeoChatRoom> m_currentRoom;
QList<QString> m_members;
bool m_active = false;
int findUserPos(const Quotient::RoomMember &member) const;
[[nodiscard]] int findUserPos(const QString &username) const;
};

View File

@@ -866,11 +866,18 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
int NeoChatRoom::getUserPowerLevel(const QString &userId) const
{
auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
if (!powerLevelEvent) {
return 0;
if (!successorId().isEmpty()) {
return 0; // No one can upgrade a room that's already upgraded
}
return powerLevelEvent->powerLevelForUser(userId);
const auto &mId = userId.isEmpty() ? connection()->userId() : userId;
if (const auto *plEvent = currentState().get<RoomPowerLevelsEvent>()) {
return plEvent->powerLevelForUser(mId);
}
if (const auto *createEvent = creation()) {
return createEvent->senderId() == mId ? 100 : 0;
}
return 0; // That's rather weird but may happen, according to rvdh
}
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)

View File

@@ -192,20 +192,7 @@ QQC2.ScrollView {
}
}
KSortFilterProxyModel {
id: sortedMessageEventModel
sourceModel: UserListModel {
room: root.room
}
sortRoleName: "powerLevel"
sortOrder: Qt.DescendingOrder
filterRoleName: "name"
filterCaseSensitivity: Qt.CaseInsensitive
}
model: root.room.isDirectChat() ? 0 : sortedMessageEventModel
model: root.room.isDirectChat() ? 0 : RoomManager.userListModel
clip: true
focus: true

View File

@@ -108,6 +108,7 @@ QQC2.ScrollView {
onTriggered: {
root.roomChanging = false;
markReadIfVisibleTimer.reset();
RoomManager.activateUserModel();
}
}
onAtYEndChanged: if (!root.roomChanging) {

View File

@@ -39,6 +39,7 @@ RoomManager::RoomManager(QObject *parent)
, m_timelineModel(new TimelineModel(this))
, m_messageFilterModel(new MessageFilterModel(this, m_timelineModel))
, m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel))
, m_userListModel(new UserListModel(this))
{
m_lastRoomConfig = m_config->group(QStringLiteral("LastOpenRoom"));
m_lastSpaceConfig = m_config->group(QStringLiteral("LastOpenSpace"));
@@ -46,6 +47,7 @@ RoomManager::RoomManager(QObject *parent)
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_timelineModel->setRoom(m_currentRoom);
m_userListModel->setRoom(m_currentRoom);
});
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this](NeoChatConnection *connection) {
@@ -113,6 +115,16 @@ MediaMessageFilterModel *RoomManager::mediaMessageFilterModel() const
return m_mediaMessageFilterModel;
}
UserListModel *RoomManager::userListModel() const
{
return m_userListModel;
}
void RoomManager::activateUserModel()
{
m_userListModel->activate();
}
UriResolveResult RoomManager::resolveResource(const Uri &uri)
{
return UriResolverBase::visitResource(m_connection, uri);

View File

@@ -22,6 +22,7 @@
#include "models/sortfilterroomtreemodel.h"
#include "models/sortfilterspacelistmodel.h"
#include "models/timelinemodel.h"
#include "models/userlistmodel.h"
class NeoChatRoom;
class NeoChatConnection;
@@ -120,6 +121,14 @@ class RoomManager : public QObject, public UriResolverBase
*/
Q_PROPERTY(MediaMessageFilterModel *mediaMessageFilterModel READ mediaMessageFilterModel CONSTANT)
/**
* @brief The UserListModel that should be used for room member visualisation.
*
* @note Available here so that the room page and drawer both have access to the
* same model.
*/
Q_PROPERTY(UserListModel *userListModel READ userListModel CONSTANT)
/**
* @brief Whether a room is currently open in NeoChat.
*
@@ -155,6 +164,9 @@ public:
MessageFilterModel *messageFilterModel() const;
MediaMessageFilterModel *mediaMessageFilterModel() const;
UserListModel *userListModel() const;
Q_INVOKABLE void activateUserModel();
/**
* @brief Resolve the given URI resource.
*
@@ -359,6 +371,9 @@ private:
TimelineModel *m_timelineModel;
MessageFilterModel *m_messageFilterModel;
MediaMessageFilterModel *m_mediaMessageFilterModel;
UserListModel *m_userListModel;
QPointer<NeoChatConnection> m_connection;
void setCurrentRoom(const QString &roomId);

View File

@@ -20,11 +20,6 @@ FormCard.FormCardPage {
title: i18nc('@title:window', 'Permissions')
property UserListModel userListModel: UserListModel {
id: userListModel
room: root.room
}
readonly property PowerLevelModel powerLevelModel: PowerLevelModel {
showMute: false
}
@@ -39,7 +34,7 @@ FormCard.FormCardPage {
FormCard.FormCard {
Repeater {
model: KSortFilterProxyModel {
sourceModel: userListModel
sourceModel: RoomManager.userListModel
sortRoleName: "powerLevel"
sortOrder: Qt.DescendingOrder
filterRowCallback: function (source_row, source_parent) {
@@ -158,7 +153,7 @@ FormCard.FormCardPage {
model: UserFilterModel {
id: userListFilterModel
sourceModel: userListModel
sourceModel: RoomManager.userListModel
filterText: userListSearchField.text
onFilterTextChanged: {