matrixion/src/timeline/RoomlistModel.cpp

964 lines
33 KiB
C++
Raw Normal View History

2021-05-19 20:34:10 +03:00
// SPDX-FileCopyrightText: 2021 Nheko Contributors
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "RoomlistModel.h"
2021-05-21 22:19:03 +03:00
#include "Cache_p.h"
2021-05-19 20:34:10 +03:00
#include "ChatPage.h"
2021-07-13 04:13:52 +03:00
#include "Logging.h"
2021-05-19 20:34:10 +03:00
#include "MatrixClient.h"
#include "MxcImageProvider.h"
#include "TimelineModel.h"
#include "TimelineViewManager.h"
#include "UserSettingsPage.h"
RoomlistModel::RoomlistModel(TimelineViewManager *parent)
2021-05-30 01:23:57 +03:00
: QAbstractListModel(parent)
, manager(parent)
2021-05-19 20:34:10 +03:00
{
2021-09-18 01:22:33 +03:00
[[maybe_unused]] static auto id = qRegisterMetaType<RoomPreview>();
connect(ChatPage::instance(), &ChatPage::decryptSidebarChanged, this, [this]() {
auto decrypt = ChatPage::instance()->userSettings()->decryptSidebar();
QHash<QString, QSharedPointer<TimelineModel>>::iterator i;
for (i = models.begin(); i != models.end(); ++i) {
auto ptr = i.value();
if (!ptr.isNull()) {
ptr->setDecryptDescription(decrypt);
ptr->updateLastMessage();
}
}
});
connect(this,
&RoomlistModel::totalUnreadMessageCountUpdated,
ChatPage::instance(),
&ChatPage::unreadMessages);
connect(
this,
&RoomlistModel::fetchedPreview,
this,
[this](QString roomid, RoomInfo info) {
if (this->previewedRooms.contains(roomid)) {
this->previewedRooms.insert(roomid, std::move(info));
auto idx = this->roomidToIndex(roomid);
emit dataChanged(index(idx),
index(idx),
{
Roles::RoomName,
Roles::AvatarUrl,
Roles::IsSpace,
Roles::IsPreviewFetched,
Qt::DisplayRole,
});
}
},
Qt::QueuedConnection);
2021-05-19 20:34:10 +03:00
}
QHash<int, QByteArray>
RoomlistModel::roleNames() const
{
2021-09-18 01:22:33 +03:00
return {
{AvatarUrl, "avatarUrl"},
{RoomName, "roomName"},
{RoomId, "roomId"},
{LastMessage, "lastMessage"},
{Time, "time"},
{Timestamp, "timestamp"},
{HasUnreadMessages, "hasUnreadMessages"},
{HasLoudNotification, "hasLoudNotification"},
{NotificationCount, "notificationCount"},
{IsInvite, "isInvite"},
{IsSpace, "isSpace"},
{Tags, "tags"},
{ParentSpaces, "parentSpaces"},
{IsDirect, "isDirect"},
{DirectChatOtherUserId, "directChatOtherUserId"},
};
2021-05-19 20:34:10 +03:00
}
QVariant
RoomlistModel::data(const QModelIndex &index, int role) const
{
2021-09-18 01:22:33 +03:00
if (index.row() >= 0 && static_cast<size_t>(index.row()) < roomids.size()) {
auto roomid = roomids.at(index.row());
if (role == Roles::ParentSpaces) {
auto parents = cache::client()->getParentRoomIds(roomid.toStdString());
QStringList list;
for (const auto &t : parents)
list.push_back(QString::fromStdString(t));
return list;
} else if (role == Roles::RoomId) {
return roomid;
}
2021-07-05 00:06:50 +03:00
2021-09-18 01:22:33 +03:00
if (models.contains(roomid)) {
auto room = models.value(roomid);
switch (role) {
case Roles::AvatarUrl:
return room->roomAvatarUrl();
case Roles::RoomName:
return room->plainRoomName();
case Roles::LastMessage:
return room->lastMessage().body;
case Roles::Time:
return room->lastMessage().descriptiveTime;
case Roles::Timestamp:
return QVariant(static_cast<quint64>(room->lastMessage().timestamp));
case Roles::HasUnreadMessages:
return this->roomReadStatus.count(roomid) && this->roomReadStatus.at(roomid);
case Roles::HasLoudNotification:
return room->hasMentions();
case Roles::NotificationCount:
return room->notificationCount();
case Roles::IsInvite:
return false;
case Roles::IsSpace:
return room->isSpace();
case Roles::IsPreview:
return false;
case Roles::Tags: {
auto info = cache::singleRoomInfo(roomid.toStdString());
QStringList list;
for (const auto &t : info.tags)
list.push_back(QString::fromStdString(t));
return list;
}
case Roles::IsDirect:
return room->isDirect();
case Roles::DirectChatOtherUserId:
return room->directChatOtherUserId();
default:
return {};
}
} else if (invites.contains(roomid)) {
auto room = invites.value(roomid);
switch (role) {
case Roles::AvatarUrl:
return QString::fromStdString(room.avatar_url);
case Roles::RoomName:
return QString::fromStdString(room.name);
case Roles::LastMessage:
return tr("Pending invite.");
case Roles::Time:
return QString();
case Roles::Timestamp:
return QVariant(static_cast<quint64>(0));
case Roles::HasUnreadMessages:
case Roles::HasLoudNotification:
return false;
case Roles::NotificationCount:
return 0;
case Roles::IsInvite:
return true;
case Roles::IsSpace:
return false;
case Roles::IsPreview:
return false;
case Roles::Tags:
return QStringList();
case Roles::IsDirect:
// The list of users from the room doesn't contain the invited
// users, so we won't factor the invite into the count
return room.member_count == 1;
case Roles::DirectChatOtherUserId:
return cache::getMembersFromInvite(roomid.toStdString(), 0, 1).front().user_id;
default:
return {};
}
} else if (previewedRooms.contains(roomid) && previewedRooms.value(roomid).has_value()) {
auto room = previewedRooms.value(roomid).value();
switch (role) {
case Roles::AvatarUrl:
return QString::fromStdString(room.avatar_url);
case Roles::RoomName:
return QString::fromStdString(room.name);
case Roles::LastMessage:
return tr("Previewing this room");
case Roles::Time:
return QString();
case Roles::Timestamp:
return QVariant(static_cast<quint64>(0));
case Roles::HasUnreadMessages:
case Roles::HasLoudNotification:
return false;
case Roles::NotificationCount:
return 0;
case Roles::IsInvite:
return false;
case Roles::IsSpace:
return room.is_space;
case Roles::IsPreview:
return true;
case Roles::IsPreviewFetched:
return true;
case Roles::Tags:
return QStringList();
case Roles::IsDirect:
return false;
case Roles::DirectChatOtherUserId:
return QString{}; // should never be reached
default:
return {};
}
2021-05-19 20:34:10 +03:00
} else {
2021-09-18 01:22:33 +03:00
if (role == Roles::IsPreview)
return true;
else if (role == Roles::IsPreviewFetched)
return false;
fetchPreview(roomid);
switch (role) {
case Roles::AvatarUrl:
return QString();
case Roles::RoomName:
return tr("No preview available");
case Roles::LastMessage:
return QString();
case Roles::Time:
return QString();
case Roles::Timestamp:
return QVariant(static_cast<quint64>(0));
case Roles::HasUnreadMessages:
case Roles::HasLoudNotification:
return false;
case Roles::NotificationCount:
return 0;
case Roles::IsInvite:
return false;
case Roles::IsSpace:
return false;
case Roles::Tags:
return QStringList();
default:
2021-05-19 20:34:10 +03:00
return {};
2021-09-18 01:22:33 +03:00
}
2021-05-19 20:34:10 +03:00
}
2021-09-18 01:22:33 +03:00
} else {
return {};
}
2021-05-19 20:34:10 +03:00
}
2021-05-21 22:19:03 +03:00
void
RoomlistModel::updateReadStatus(const std::map<QString, bool> roomReadStatus_)
{
2021-09-18 01:22:33 +03:00
std::vector<int> roomsToUpdate;
roomsToUpdate.resize(roomReadStatus_.size());
for (const auto &[roomid, roomUnread] : roomReadStatus_) {
if (roomUnread != roomReadStatus[roomid]) {
roomsToUpdate.push_back(this->roomidToIndex(roomid));
2021-05-22 01:57:14 +03:00
}
2021-05-21 22:19:03 +03:00
2021-09-18 01:22:33 +03:00
this->roomReadStatus[roomid] = roomUnread;
}
for (auto idx : roomsToUpdate) {
emit dataChanged(index(idx),
index(idx),
{
Roles::HasUnreadMessages,
});
}
2021-05-24 15:04:07 +03:00
}
2021-05-19 20:34:10 +03:00
void
RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
{
2021-09-18 01:22:33 +03:00
if (!models.contains(room_id)) {
// ensure we get read status updates and are only connected once
connect(cache::client(),
&Cache::roomReadStatus,
this,
&RoomlistModel::updateReadStatus,
Qt::UniqueConnection);
QSharedPointer<TimelineModel> newRoom(new TimelineModel(manager, room_id));
newRoom->setDecryptDescription(ChatPage::instance()->userSettings()->decryptSidebar());
connect(newRoom.data(),
&TimelineModel::newEncryptedImage,
manager->imageProvider(),
&MxcImageProvider::addEncryptionInfo);
connect(newRoom.data(),
&TimelineModel::forwardToRoom,
manager,
&TimelineViewManager::forwardMessageToRoom);
connect(newRoom.data(), &TimelineModel::lastMessageChanged, this, [room_id, this]() {
auto idx = this->roomidToIndex(room_id);
emit dataChanged(index(idx),
index(idx),
{
Roles::HasLoudNotification,
Roles::LastMessage,
Roles::Timestamp,
Roles::NotificationCount,
Qt::DisplayRole,
});
});
connect(newRoom.data(), &TimelineModel::roomAvatarUrlChanged, this, [room_id, this]() {
auto idx = this->roomidToIndex(room_id);
emit dataChanged(index(idx),
index(idx),
{
Roles::AvatarUrl,
});
});
connect(newRoom.data(), &TimelineModel::roomNameChanged, this, [room_id, this]() {
auto idx = this->roomidToIndex(room_id);
emit dataChanged(index(idx),
index(idx),
{
Roles::RoomName,
});
});
connect(newRoom.data(), &TimelineModel::notificationsChanged, this, [room_id, this]() {
auto idx = this->roomidToIndex(room_id);
emit dataChanged(index(idx),
index(idx),
{
Roles::HasLoudNotification,
Roles::NotificationCount,
Qt::DisplayRole,
});
int total_unread_msgs = 0;
for (const auto &room : models) {
if (!room.isNull())
total_unread_msgs += room->notificationCount();
}
emit totalUnreadMessageCountUpdated(total_unread_msgs);
});
2021-05-24 15:04:07 +03:00
2021-09-18 01:22:33 +03:00
newRoom->updateLastMessage();
2021-05-24 15:04:07 +03:00
2021-09-18 01:22:33 +03:00
std::vector<QString> previewsToAdd;
if (newRoom->isSpace()) {
auto childs = cache::client()->getChildRoomIds(room_id.toStdString());
for (const auto &c : childs) {
auto id = QString::fromStdString(c);
if (!(models.contains(id) || invites.contains(id) || previewedRooms.contains(id))) {
previewsToAdd.push_back(std::move(id));
2021-07-13 04:13:52 +03:00
}
2021-09-18 01:22:33 +03:00
}
}
2021-07-13 04:13:52 +03:00
2021-09-18 01:22:33 +03:00
bool wasInvite = invites.contains(room_id);
bool wasPreview = previewedRooms.contains(room_id);
if (!suppressInsertNotification && ((!wasInvite && !wasPreview) || !previewedRooms.empty()))
// if the old room was already in the list, don't add it. Also add all
// previews at the same time.
beginInsertRows(
QModelIndex(),
(int)roomids.size(),
(int)(roomids.size() + previewsToAdd.size() - ((wasInvite || wasPreview) ? 1 : 0)));
models.insert(room_id, std::move(newRoom));
if (wasInvite) {
auto idx = roomidToIndex(room_id);
invites.remove(room_id);
emit dataChanged(index(idx), index(idx));
} else if (wasPreview) {
auto idx = roomidToIndex(room_id);
previewedRooms.remove(room_id);
emit dataChanged(index(idx), index(idx));
} else {
roomids.push_back(room_id);
}
2021-07-05 00:06:50 +03:00
2021-09-18 01:22:33 +03:00
if ((wasInvite || wasPreview) && currentRoomPreview_ &&
currentRoomPreview_->roomid() == room_id) {
currentRoom_ = models.value(room_id);
currentRoomPreview_.reset();
emit currentRoomChanged();
}
2021-09-18 01:22:33 +03:00
for (auto p : previewsToAdd) {
previewedRooms.insert(p, std::nullopt);
roomids.push_back(std::move(p));
2021-05-19 20:34:10 +03:00
}
2021-09-18 01:22:33 +03:00
if (!suppressInsertNotification && ((!wasInvite && !wasPreview) || !previewedRooms.empty()))
endInsertRows();
emit ChatPage::instance()->newRoom(room_id);
}
2021-05-19 20:34:10 +03:00
}
2021-07-05 00:06:50 +03:00
void
RoomlistModel::fetchPreview(QString roomid_) const
{
2021-09-18 01:22:33 +03:00
std::string roomid = roomid_.toStdString();
http::client()->get_state_event<mtx::events::state::Create>(
roomid, "", [this, roomid](const mtx::events::state::Create &c, mtx::http::RequestErr err) {
bool is_space = false;
if (!err) {
is_space = c.type == mtx::events::state::room_type::space;
}
http::client()->get_state_event<mtx::events::state::Avatar>(
roomid,
"",
[this, roomid, is_space](const mtx::events::state::Avatar &a, mtx::http::RequestErr) {
auto avatar_url = a.url;
http::client()->get_state_event<mtx::events::state::Topic>(
roomid,
"",
[this, roomid, avatar_url, is_space](const mtx::events::state::Topic &t,
mtx::http::RequestErr) {
auto topic = t.topic;
http::client()->get_state_event<mtx::events::state::Name>(
roomid,
"",
[this, roomid, topic, avatar_url, is_space](
const mtx::events::state::Name &n, mtx::http::RequestErr err) {
if (err) {
nhlog::net()->warn("Failed to fetch name event to "
"create preview for {}",
roomid);
}
// don't even add a preview, if we got not a single
// response
if (n.name.empty() && avatar_url.empty() && topic.empty())
return;
RoomInfo info{};
info.name = n.name;
info.is_space = is_space;
info.avatar_url = avatar_url;
info.topic = topic;
const_cast<RoomlistModel *>(this)->fetchedPreview(
QString::fromStdString(roomid), info);
});
});
});
});
2021-07-05 00:06:50 +03:00
}
2021-05-19 20:34:10 +03:00
void
RoomlistModel::sync(const mtx::responses::Rooms &rooms)
{
2021-09-18 01:22:33 +03:00
for (const auto &[room_id, room] : rooms.join) {
auto qroomid = QString::fromStdString(room_id);
// addRoom will only add the room, if it doesn't exist
addRoom(qroomid);
const auto &room_model = models.value(qroomid);
room_model->sync(room);
// room_model->addEvents(room.timeline);
connect(room_model.data(),
&TimelineModel::newCallEvent,
manager->callManager(),
&CallManager::syncEvent,
Qt::UniqueConnection);
if (ChatPage::instance()->userSettings()->typingNotifications()) {
for (const auto &ev : room.ephemeral.events) {
if (auto t =
std::get_if<mtx::events::EphemeralEvent<mtx::events::ephemeral::Typing>>(
&ev)) {
std::vector<QString> typing;
typing.reserve(t->content.user_ids.size());
for (const auto &user : t->content.user_ids) {
if (user != http::client()->user_id().to_string())
typing.push_back(QString::fromStdString(user));
}
room_model->updateTypingUsers(typing);
2021-05-19 20:34:10 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-05-19 20:34:10 +03:00
}
2021-09-18 01:22:33 +03:00
}
for (const auto &[room_id, room] : rooms.leave) {
(void)room;
auto qroomid = QString::fromStdString(room_id);
if ((currentRoom_ && currentRoom_->roomId() == qroomid) ||
(currentRoomPreview_ && currentRoomPreview_->roomid() == qroomid))
resetCurrentRoom();
auto idx = this->roomidToIndex(qroomid);
if (idx != -1) {
beginRemoveRows(QModelIndex(), idx, idx);
roomids.erase(roomids.begin() + idx);
if (models.contains(qroomid))
models.remove(qroomid);
else if (invites.contains(qroomid))
invites.remove(qroomid);
endRemoveRows();
2021-05-22 12:23:16 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-05-24 15:04:07 +03:00
2021-09-18 01:22:33 +03:00
for (const auto &[room_id, room] : rooms.invite) {
(void)room;
auto qroomid = QString::fromStdString(room_id);
auto invite = cache::client()->invite(room_id);
if (!invite)
continue;
if (invites.contains(qroomid)) {
invites[qroomid] = *invite;
auto idx = roomidToIndex(qroomid);
emit dataChanged(index(idx), index(idx));
} else {
beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size());
invites.insert(qroomid, *invite);
roomids.push_back(std::move(qroomid));
endInsertRows();
2021-05-24 15:04:07 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-05-19 20:34:10 +03:00
}
void
2021-05-24 15:04:07 +03:00
RoomlistModel::initializeRooms()
2021-05-19 20:34:10 +03:00
{
2021-09-18 01:22:33 +03:00
beginResetModel();
models.clear();
roomids.clear();
invites.clear();
currentRoom_ = nullptr;
2021-05-24 15:04:07 +03:00
2021-09-18 01:22:33 +03:00
invites = cache::client()->invites();
for (const auto &id : invites.keys())
roomids.push_back(id);
2021-05-24 15:04:07 +03:00
2021-09-18 01:22:33 +03:00
for (const auto &id : cache::client()->roomIds())
addRoom(id, true);
2021-05-24 15:04:07 +03:00
2021-09-18 01:22:33 +03:00
nhlog::db()->info("Restored {} rooms from cache", rowCount());
2021-07-30 04:31:49 +03:00
2021-09-18 01:22:33 +03:00
endResetModel();
2021-05-19 20:34:10 +03:00
}
void
RoomlistModel::clear()
{
2021-09-18 01:22:33 +03:00
beginResetModel();
models.clear();
invites.clear();
roomids.clear();
currentRoom_ = nullptr;
emit currentRoomChanged();
endResetModel();
2021-05-19 20:34:10 +03:00
}
2021-05-22 01:57:14 +03:00
void
RoomlistModel::joinPreview(QString roomid, QString parentSpace)
{
2021-09-18 01:22:33 +03:00
if (previewedRooms.contains(roomid)) {
auto child = cache::client()->getStateEvent<mtx::events::state::space::Child>(
parentSpace.toStdString(), roomid.toStdString());
ChatPage::instance()->joinRoomVia(
roomid.toStdString(),
(child && child->content.via) ? child->content.via.value() : std::vector<std::string>{},
false);
}
}
2021-05-24 15:04:07 +03:00
void
RoomlistModel::acceptInvite(QString roomid)
{
2021-09-18 01:22:33 +03:00
if (invites.contains(roomid)) {
// Don't remove invite yet, so that we can switch to it
ChatPage::instance()->joinRoom(roomid);
}
2021-05-24 15:04:07 +03:00
}
void
RoomlistModel::declineInvite(QString roomid)
{
2021-09-18 01:22:33 +03:00
if (invites.contains(roomid)) {
auto idx = roomidToIndex(roomid);
if (idx != -1) {
beginRemoveRows(QModelIndex(), idx, idx);
roomids.erase(roomids.begin() + idx);
invites.remove(roomid);
endRemoveRows();
ChatPage::instance()->leaveRoom(roomid);
2021-05-24 15:04:07 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-05-24 15:04:07 +03:00
}
2021-05-28 18:25:46 +03:00
void
RoomlistModel::leave(QString roomid)
{
2021-09-18 01:22:33 +03:00
if (models.contains(roomid)) {
auto idx = roomidToIndex(roomid);
if (idx != -1) {
beginRemoveRows(QModelIndex(), idx, idx);
roomids.erase(roomids.begin() + idx);
models.remove(roomid);
endRemoveRows();
ChatPage::instance()->leaveRoom(roomid);
2021-05-28 18:25:46 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-05-28 18:25:46 +03:00
}
2021-05-24 15:04:07 +03:00
void
RoomlistModel::setCurrentRoom(QString roomid)
{
2021-09-18 01:22:33 +03:00
if ((currentRoom_ && currentRoom_->roomId() == roomid) ||
(currentRoomPreview_ && currentRoomPreview_->roomid() == roomid))
return;
nhlog::ui()->debug("Trying to switch to: {}", roomid.toStdString());
if (models.contains(roomid)) {
currentRoom_ = models.value(roomid);
currentRoomPreview_.reset();
emit currentRoomChanged();
nhlog::ui()->debug("Switched to: {}", roomid.toStdString());
} else if (invites.contains(roomid) || previewedRooms.contains(roomid)) {
currentRoom_ = nullptr;
std::optional<RoomInfo> i;
2021-09-18 01:22:33 +03:00
RoomPreview p;
2021-09-18 01:22:33 +03:00
if (invites.contains(roomid)) {
i = invites.value(roomid);
p.isInvite_ = true;
} else {
i = previewedRooms.value(roomid);
p.isInvite_ = false;
}
2021-09-18 01:22:33 +03:00
if (i) {
p.roomid_ = roomid;
p.roomName_ = QString::fromStdString(i->name);
p.roomTopic_ = QString::fromStdString(i->topic);
p.roomAvatarUrl_ = QString::fromStdString(i->avatar_url);
currentRoomPreview_ = std::move(p);
}
2021-09-18 01:22:33 +03:00
emit currentRoomChanged();
nhlog::ui()->debug("Switched to: {}", roomid.toStdString());
}
}
2021-05-22 01:57:14 +03:00
namespace {
enum NotificationImportance : short
{
2021-09-18 01:22:33 +03:00
ImportanceDisabled = -3,
NoPreview = -2,
Preview = -1,
AllEventsRead = 0,
NewMessage = 1,
NewMentions = 2,
Invite = 3,
SubSpace = 4,
CurrentSpace = 5,
2021-05-22 01:57:14 +03:00
};
}
short int
FilteredRoomlistModel::calculateImportance(const QModelIndex &idx) const
{
2021-09-18 01:22:33 +03:00
// Returns the degree of importance of the unread messages in the room.
// If sorting by importance is disabled in settings, this only ever
// returns ImportanceDisabled or Invite
if (sourceModel()->data(idx, RoomlistModel::IsSpace).toBool()) {
if (filterType == FilterBy::Space &&
filterStr == sourceModel()->data(idx, RoomlistModel::RoomId).toString())
return CurrentSpace;
else
return SubSpace;
} else if (sourceModel()->data(idx, RoomlistModel::IsPreview).toBool()) {
if (sourceModel()->data(idx, RoomlistModel::IsPreviewFetched).toBool())
return Preview;
else
return NoPreview;
} else if (sourceModel()->data(idx, RoomlistModel::IsInvite).toBool()) {
return Invite;
} else if (!this->sortByImportance) {
return ImportanceDisabled;
} else if (sourceModel()->data(idx, RoomlistModel::HasLoudNotification).toBool()) {
return NewMentions;
} else if (sourceModel()->data(idx, RoomlistModel::NotificationCount).toInt() > 0) {
return NewMessage;
} else {
return AllEventsRead;
}
2021-05-22 01:57:14 +03:00
}
2021-07-05 00:06:50 +03:00
2021-05-22 01:57:14 +03:00
bool
FilteredRoomlistModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
2021-09-18 01:22:33 +03:00
QModelIndex const left_idx = sourceModel()->index(left.row(), 0, QModelIndex());
QModelIndex const right_idx = sourceModel()->index(right.row(), 0, QModelIndex());
// Sort by "importance" (i.e. invites before mentions before
// notifs before new events before old events), then secondly
// by recency.
// Checking importance first
const auto a_importance = calculateImportance(left_idx);
const auto b_importance = calculateImportance(right_idx);
if (a_importance != b_importance) {
return a_importance > b_importance;
}
// Now sort by recency
// Zero if empty, otherwise the time that the event occured
uint64_t a_recency = sourceModel()->data(left_idx, RoomlistModel::Timestamp).toULongLong();
uint64_t b_recency = sourceModel()->data(right_idx, RoomlistModel::Timestamp).toULongLong();
if (a_recency != b_recency)
return a_recency > b_recency;
else
return left.row() < right.row();
2021-05-22 01:57:14 +03:00
}
FilteredRoomlistModel::FilteredRoomlistModel(RoomlistModel *model, QObject *parent)
: QSortFilterProxyModel(parent)
, roomlistmodel(model)
{
2021-09-18 01:22:33 +03:00
this->sortByImportance = UserSettings::instance()->sortByImportance();
setSourceModel(model);
setDynamicSortFilter(true);
QObject::connect(UserSettings::instance().get(),
&UserSettings::roomSortingChanged,
this,
[this](bool sortByImportance_) {
this->sortByImportance = sortByImportance_;
invalidate();
});
connect(roomlistmodel,
&RoomlistModel::currentRoomChanged,
this,
&FilteredRoomlistModel::currentRoomChanged);
sort(0);
2021-05-22 01:57:14 +03:00
}
2021-05-28 18:25:46 +03:00
2021-06-11 18:54:05 +03:00
void
FilteredRoomlistModel::updateHiddenTagsAndSpaces()
{
2021-09-18 01:22:33 +03:00
hiddenTags.clear();
hiddenSpaces.clear();
for (const auto &t : UserSettings::instance()->hiddenTags()) {
if (t.startsWith("tag:"))
hiddenTags.push_back(t.mid(4));
else if (t.startsWith("space:"))
hiddenSpaces.push_back(t.mid(6));
}
invalidateFilter();
2021-06-11 18:54:05 +03:00
}
bool
FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const
{
2021-09-18 01:22:33 +03:00
if (filterType == FilterBy::Nothing) {
if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
.toBool()) {
return false;
}
2021-07-05 00:06:50 +03:00
2021-09-18 01:22:33 +03:00
if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
.toBool()) {
return false;
}
2021-09-18 01:22:33 +03:00
if (!hiddenTags.empty()) {
auto tags = sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
.toStringList();
2021-06-11 18:54:05 +03:00
2021-09-18 01:22:33 +03:00
for (const auto &t : tags)
if (hiddenTags.contains(t))
return false;
}
2021-09-18 01:22:33 +03:00
if (!hiddenSpaces.empty()) {
auto parents = sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
.toStringList();
for (const auto &t : parents)
if (hiddenSpaces.contains(t))
return false;
}
2021-06-11 18:54:05 +03:00
2021-09-18 01:22:33 +03:00
return true;
} else if (filterType == FilterBy::Tag) {
if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
.toBool()) {
return false;
}
2021-07-05 00:06:50 +03:00
2021-09-18 01:22:33 +03:00
if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
.toBool()) {
return false;
}
2021-09-18 01:22:33 +03:00
auto tags = sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
.toStringList();
2021-06-11 18:54:05 +03:00
2021-09-18 01:22:33 +03:00
if (!tags.contains(filterStr))
return false;
2021-09-18 01:22:33 +03:00
if (!hiddenTags.empty()) {
for (const auto &t : tags)
if (t != filterStr && hiddenTags.contains(t))
return false;
}
2021-09-18 01:22:33 +03:00
if (!hiddenSpaces.empty()) {
auto parents = sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
.toStringList();
for (const auto &t : parents)
if (hiddenSpaces.contains(t))
return false;
}
2021-09-18 01:22:33 +03:00
return true;
} else if (filterType == FilterBy::Space) {
if (filterStr == sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::RoomId)
.toString())
return true;
2021-09-18 01:22:33 +03:00
auto parents = sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
.toStringList();
2021-09-18 01:22:33 +03:00
if (!parents.contains(filterStr))
return false;
2021-09-18 01:22:33 +03:00
if (!hiddenTags.empty()) {
auto tags = sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::Tags)
.toStringList();
for (const auto &t : tags)
if (hiddenTags.contains(t))
return false;
}
if (!hiddenSpaces.empty()) {
for (const auto &t : parents)
if (t != filterStr && hiddenSpaces.contains(t))
2021-09-18 01:22:33 +03:00
return false;
}
if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
.toBool() &&
!parents.contains(filterStr)) {
return false;
2021-06-11 18:54:05 +03:00
}
2021-09-18 01:22:33 +03:00
return true;
} else {
return true;
}
2021-06-11 18:54:05 +03:00
}
2021-05-28 18:25:46 +03:00
void
FilteredRoomlistModel::toggleTag(QString roomid, QString tag, bool on)
{
2021-09-18 01:22:33 +03:00
if (on) {
http::client()->put_tag(
roomid.toStdString(), tag.toStdString(), {}, [tag](mtx::http::RequestErr err) {
if (err) {
nhlog::ui()->error(
"Failed to add tag: {}, {}", tag.toStdString(), err->matrix_error.error);
}
});
} else {
http::client()->delete_tag(
roomid.toStdString(), tag.toStdString(), [tag](mtx::http::RequestErr err) {
if (err) {
nhlog::ui()->error(
"Failed to delete tag: {}, {}", tag.toStdString(), err->matrix_error.error);
}
});
}
2021-05-28 18:25:46 +03:00
}
2021-05-29 00:25:57 +03:00
void
FilteredRoomlistModel::nextRoomWithActivity()
{
2021-09-18 01:22:33 +03:00
int roomWithMention = -1;
int roomWithNotification = -1;
int roomWithUnreadMessage = -1;
auto r = currentRoom();
int currentRoomIdx = r ? roomidToIndex(r->roomId()) : -1;
// first look for mentions
for (int i = 0; i < (int)roomlistmodel->roomids.size(); i++) {
if (i == currentRoomIdx)
continue;
if (this->data(index(i, 0), RoomlistModel::HasLoudNotification).toBool()) {
roomWithMention = i;
break;
}
2021-09-18 01:22:33 +03:00
if (roomWithNotification == -1 &&
this->data(index(i, 0), RoomlistModel::NotificationCount).toInt() > 0) {
roomWithNotification = i;
// don't break, we must continue looking for rooms with mentions
}
2021-09-18 01:22:33 +03:00
if (roomWithNotification == -1 && roomWithUnreadMessage == -1 &&
this->data(index(i, 0), RoomlistModel::HasUnreadMessages).toBool()) {
roomWithUnreadMessage = i;
// don't break, we must continue looking for rooms with mentions
}
2021-09-18 01:22:33 +03:00
}
QString targetRoomId = nullptr;
if (roomWithMention != -1) {
targetRoomId = this->data(index(roomWithMention, 0), RoomlistModel::RoomId).toString();
nhlog::ui()->debug("choosing {} for mentions", targetRoomId.toStdString());
} else if (roomWithNotification != -1) {
targetRoomId = this->data(index(roomWithNotification, 0), RoomlistModel::RoomId).toString();
nhlog::ui()->debug("choosing {} for notifications", targetRoomId.toStdString());
} else if (roomWithUnreadMessage != -1) {
targetRoomId =
this->data(index(roomWithUnreadMessage, 0), RoomlistModel::RoomId).toString();
nhlog::ui()->debug("choosing {} for unread messages", targetRoomId.toStdString());
}
if (targetRoomId != nullptr) {
setCurrentRoom(targetRoomId);
}
}
2021-05-29 00:25:57 +03:00
void
FilteredRoomlistModel::nextRoom()
{
2021-09-18 01:22:33 +03:00
auto r = currentRoom();
if (r) {
int idx = roomidToIndex(r->roomId());
idx++;
if (idx < rowCount()) {
setCurrentRoom(data(index(idx, 0), RoomlistModel::Roles::RoomId).toString());
2021-05-29 00:25:57 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-05-29 00:25:57 +03:00
}
void
FilteredRoomlistModel::previousRoom()
{
2021-09-18 01:22:33 +03:00
auto r = currentRoom();
if (r) {
int idx = roomidToIndex(r->roomId());
idx--;
if (idx >= 0) {
setCurrentRoom(data(index(idx, 0), RoomlistModel::Roles::RoomId).toString());
2021-05-29 00:25:57 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-05-29 00:25:57 +03:00
}