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.
239 lines
6.6 KiB
C++
239 lines
6.6 KiB
C++
// SPDX-FileCopyrightText: 2018-2020 Black Hat <bhat@encom.eu.org>
|
|
// SPDX-License-Identifier: GPL-3.0-only
|
|
|
|
#include "userlistmodel.h"
|
|
|
|
#include <QGuiApplication>
|
|
|
|
#include <Quotient/avatar.h>
|
|
#include <Quotient/events/roompowerlevelsevent.h>
|
|
#include <Quotient/room.h>
|
|
|
|
#include "enums/powerlevel.h"
|
|
#include "neochatroom.h"
|
|
|
|
using namespace Quotient;
|
|
|
|
UserListModel::UserListModel(QObject *parent)
|
|
: QAbstractListModel(parent)
|
|
, m_currentRoom(nullptr)
|
|
{
|
|
}
|
|
|
|
void UserListModel::setRoom(NeoChatRoom *room)
|
|
{
|
|
if (m_currentRoom == room) {
|
|
return;
|
|
}
|
|
|
|
if (m_currentRoom) {
|
|
// HACK: Reset the model to a null room first to make sure QML dismantles
|
|
// last room's objects before the room is actually changed
|
|
beginResetModel();
|
|
m_currentRoom->disconnect(this);
|
|
m_currentRoom->connection()->disconnect(this);
|
|
m_currentRoom = nullptr;
|
|
m_members.clear();
|
|
endResetModel();
|
|
}
|
|
|
|
m_currentRoom = room;
|
|
|
|
if (m_currentRoom) {
|
|
connect(m_currentRoom, &Room::memberJoined, this, &UserListModel::memberJoined);
|
|
connect(m_currentRoom, &Room::memberLeft, this, &UserListModel::memberLeft);
|
|
connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) {
|
|
refreshMember(member, {DisplayNameRole});
|
|
});
|
|
connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
|
|
refreshMember(member, {AvatarRole});
|
|
});
|
|
connect(m_currentRoom, &Room::memberListChanged, this, [this]() {
|
|
// this is slow
|
|
UserListModel::refreshAllMembers();
|
|
});
|
|
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
|
setRoom(nullptr);
|
|
});
|
|
}
|
|
|
|
m_active = false;
|
|
Q_EMIT roomChanged();
|
|
}
|
|
|
|
NeoChatRoom *UserListModel::room() const
|
|
{
|
|
return m_currentRoom;
|
|
}
|
|
|
|
QVariant UserListModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!m_currentRoom) {
|
|
return {};
|
|
}
|
|
if (!index.isValid()) {
|
|
return QVariant();
|
|
}
|
|
|
|
if (index.row() >= m_members.count()) {
|
|
qDebug() << "UserListModel, something's wrong: index.row() >= "
|
|
"users.count()";
|
|
return {};
|
|
}
|
|
auto memberId = m_members.at(index.row());
|
|
if (role == DisplayNameRole) {
|
|
return m_currentRoom->member(memberId).disambiguatedName();
|
|
}
|
|
if (role == UserIdRole) {
|
|
return memberId;
|
|
}
|
|
if (role == AvatarRole) {
|
|
return m_currentRoom->memberAvatar(memberId).url();
|
|
}
|
|
if (role == ObjectRole) {
|
|
return QVariant::fromValue(memberId);
|
|
}
|
|
if (role == PowerLevelRole) {
|
|
auto plEvent = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
|
|
if (!plEvent) {
|
|
return 0;
|
|
}
|
|
return plEvent->powerLevelForUser(memberId);
|
|
}
|
|
if (role == PowerLevelStringRole) {
|
|
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
|
|
// User might not in the room yet, in this case pl can be nullptr.
|
|
// e.g. When invited but user not accepted or denied the invitation.
|
|
if (!pl) {
|
|
return QStringLiteral("Not Available");
|
|
}
|
|
|
|
auto userPl = pl->powerLevelForUser(memberId);
|
|
|
|
return i18nc("%1 is the name of the power level, e.g. admin and %2 is the value that represents.",
|
|
"%1 (%2)",
|
|
PowerLevel::nameForLevel(PowerLevel::levelForValue(userPl)),
|
|
userPl);
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
int UserListModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid()) {
|
|
return 0;
|
|
}
|
|
return m_members.count();
|
|
}
|
|
|
|
bool UserListModel::event(QEvent *event)
|
|
{
|
|
if (event->type() == QEvent::ApplicationPaletteChange) {
|
|
refreshAllMembers();
|
|
}
|
|
return QObject::event(event);
|
|
}
|
|
|
|
void UserListModel::memberJoined(const Quotient::RoomMember &member)
|
|
{
|
|
auto pos = findUserPos(member);
|
|
beginInsertRows(QModelIndex(), pos, pos);
|
|
m_members.insert(pos, member.id());
|
|
endInsertRows();
|
|
}
|
|
|
|
void UserListModel::memberLeft(const Quotient::RoomMember &member)
|
|
{
|
|
auto pos = findUserPos(member);
|
|
if (pos != m_members.size()) {
|
|
beginRemoveRows(QModelIndex(), pos, pos);
|
|
m_members.removeAt(pos);
|
|
endRemoveRows();
|
|
} else {
|
|
qWarning() << "Trying to remove a room member not in the user list";
|
|
}
|
|
}
|
|
|
|
void UserListModel::refreshMember(const Quotient::RoomMember &member, const QList<int> &roles)
|
|
{
|
|
auto pos = findUserPos(member);
|
|
if (pos != m_members.size()) {
|
|
// The update will have changed the state event so we need to insert the updated member object.
|
|
m_members.insert(pos, member.id());
|
|
Q_EMIT dataChanged(index(pos), index(pos), roles);
|
|
} else {
|
|
qWarning() << "Trying to access a room member not in the user list";
|
|
}
|
|
}
|
|
|
|
void UserListModel::refreshAllMembers()
|
|
{
|
|
beginResetModel();
|
|
|
|
if (m_currentRoom != nullptr) {
|
|
m_members = m_currentRoom->joinedMemberIds();
|
|
#if Quotient_VERSION_MINOR > 8
|
|
MemberSorter sorter;
|
|
#else
|
|
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();
|
|
}
|
|
|
|
int UserListModel::findUserPos(const RoomMember &member) const
|
|
{
|
|
return findUserPos(member.id());
|
|
}
|
|
|
|
int UserListModel::findUserPos(const QString &userId) const
|
|
{
|
|
if (!m_currentRoom) {
|
|
return 0;
|
|
}
|
|
const auto pos = std::find_if(m_members.cbegin(), m_members.cend(), [&userId](const QString &memberId) {
|
|
return userId == memberId;
|
|
});
|
|
return pos - m_members.cbegin();
|
|
}
|
|
|
|
QHash<int, QByteArray> UserListModel::roleNames() const
|
|
{
|
|
QHash<int, QByteArray> roles;
|
|
|
|
roles[DisplayNameRole] = "name";
|
|
roles[UserIdRole] = "userId";
|
|
roles[AvatarRole] = "avatar";
|
|
roles[ObjectRole] = "user";
|
|
roles[PowerLevelRole] = "powerLevel";
|
|
roles[PowerLevelStringRole] = "powerLevelString";
|
|
|
|
return roles;
|
|
}
|
|
|
|
void UserListModel::activate()
|
|
{
|
|
if (m_active) {
|
|
return;
|
|
}
|
|
|
|
m_active = true;
|
|
refreshAllMembers();
|
|
}
|
|
|
|
#include "moc_userlistmodel.cpp"
|