matrixion/src/timeline/CommunitiesModel.cpp

903 lines
32 KiB
C++
Raw Normal View History

2021-06-10 00:52:28 +03:00
// SPDX-FileCopyrightText: 2021 Nheko Contributors
// SPDX-FileCopyrightText: 2022 Nheko Contributors
2021-06-10 00:52:28 +03:00
//
// SPDX-License-Identifier: GPL-3.0-or-later
#include "CommunitiesModel.h"
2022-08-10 01:20:44 +03:00
#include <mtx/responses/common.hpp>
2021-06-10 00:52:28 +03:00
#include <set>
#include "Cache.h"
2021-12-01 02:02:41 +03:00
#include "Cache_p.h"
#include "ChatPage.h"
2021-12-01 02:02:41 +03:00
#include "Logging.h"
2022-08-10 01:20:44 +03:00
#include "MatrixClient.h"
#include "Permissions.h"
2021-06-10 00:52:28 +03:00
#include "UserSettingsPage.h"
2022-04-20 06:18:11 +03:00
#include "Utils.h"
2022-08-10 01:20:44 +03:00
#include "timeline/TimelineModel.h"
Q_DECLARE_METATYPE(SpaceItem)
2021-06-10 00:52:28 +03:00
CommunitiesModel::CommunitiesModel(QObject *parent)
: QAbstractListModel(parent)
, hiddenTagIds_{UserSettings::instance()->hiddenTags()}
, mutedTagIds_{UserSettings::instance()->mutedTags()}
2022-08-10 01:20:44 +03:00
{
static auto ignore = qRegisterMetaType<SpaceItem>();
(void)ignore;
}
2021-06-10 00:52:28 +03:00
QHash<int, QByteArray>
CommunitiesModel::roleNames() const
{
2021-09-18 01:22:33 +03:00
return {
{AvatarUrl, "avatarUrl"},
{DisplayName, "displayName"},
{Tooltip, "tooltip"},
2021-12-01 02:02:41 +03:00
{Collapsed, "collapsed"},
{Collapsible, "collapsible"},
2021-09-18 01:22:33 +03:00
{Hidden, "hidden"},
2021-12-01 02:02:41 +03:00
{Depth, "depth"},
2021-09-18 01:22:33 +03:00
{Id, "id"},
{UnreadMessages, "unreadMessages"},
2022-04-21 04:30:16 +03:00
{HasLoudNotification, "hasLoudNotification"},
{Muted, "muted"},
2021-09-18 01:22:33 +03:00
};
2021-06-10 00:52:28 +03:00
}
2021-12-01 02:02:41 +03:00
bool
CommunitiesModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (role != CommunitiesModel::Collapsed)
return false;
else if (index.row() >= 2 || index.row() - 2 < spaceOrder_.size()) {
spaceOrder_.tree.at(index.row() - 2).collapsed = value.toBool();
const auto cindex = spaceOrder_.lastChild(index.row() - 2);
emit dataChanged(index, this->index(cindex + 2), {Collapsed, Qt::DisplayRole});
2021-12-01 05:46:55 +03:00
spaceOrder_.storeCollapsed();
2021-12-01 02:02:41 +03:00
return true;
} else
return false;
}
2021-06-10 00:52:28 +03:00
QVariant
CommunitiesModel::data(const QModelIndex &index, int role) const
{
if (role == CommunitiesModel::Roles::Muted) {
if (index.row() == 0)
return mutedTagIds_.contains(QStringLiteral("global"));
else
return mutedTagIds_.contains(data(index, CommunitiesModel::Roles::Id).toString());
}
2021-09-18 01:22:33 +03:00
if (index.row() == 0) {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QStringLiteral(":/icons/icons/ui/world.svg");
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::DisplayName:
return tr("All rooms");
case CommunitiesModel::Roles::Tooltip:
return tr("Shows all rooms without filtering.");
2021-12-01 02:02:41 +03:00
case CommunitiesModel::Roles::Collapsed:
return false;
case CommunitiesModel::Roles::Collapsible:
2021-09-18 01:22:33 +03:00
return false;
case CommunitiesModel::Roles::Hidden:
return false;
2021-12-01 02:02:41 +03:00
case CommunitiesModel::Roles::Parent:
return "";
case CommunitiesModel::Roles::Depth:
return 0;
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::Id:
return "";
2022-07-15 17:19:06 +03:00
case CommunitiesModel::Roles::UnreadMessages:
return (int)globalUnreads.notification_count;
2022-04-21 04:30:16 +03:00
case CommunitiesModel::Roles::HasLoudNotification:
2022-07-15 17:19:06 +03:00
return globalUnreads.highlight_count > 0;
2021-09-18 01:22:33 +03:00
}
} else if (index.row() == 1) {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QStringLiteral(":/icons/icons/ui/people.svg");
case CommunitiesModel::Roles::DisplayName:
return tr("Direct Chats");
case CommunitiesModel::Roles::Tooltip:
return tr("Show direct chats.");
2021-12-01 02:02:41 +03:00
case CommunitiesModel::Roles::Collapsed:
return false;
case CommunitiesModel::Roles::Collapsible:
return false;
case CommunitiesModel::Roles::Hidden:
2022-04-20 03:49:37 +03:00
return hiddenTagIds_.contains(QStringLiteral("dm"));
2021-12-01 02:02:41 +03:00
case CommunitiesModel::Roles::Parent:
return "";
case CommunitiesModel::Roles::Depth:
return 0;
case CommunitiesModel::Roles::Id:
return "dm";
2022-07-15 17:19:06 +03:00
case CommunitiesModel::Roles::UnreadMessages:
return (int)dmUnreads.notification_count;
2022-04-21 04:30:16 +03:00
case CommunitiesModel::Roles::HasLoudNotification:
2022-07-15 17:19:06 +03:00
return dmUnreads.highlight_count > 0;
}
} else if (index.row() - 2 < spaceOrder_.size()) {
auto id = spaceOrder_.tree.at(index.row() - 2).id;
2021-09-18 01:22:33 +03:00
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString::fromStdString(spaces_.at(id).avatar_url);
case CommunitiesModel::Roles::DisplayName:
case CommunitiesModel::Roles::Tooltip:
return QString::fromStdString(spaces_.at(id).name);
2021-12-01 02:02:41 +03:00
case CommunitiesModel::Roles::Collapsed:
return spaceOrder_.tree.at(index.row() - 2).collapsed;
case CommunitiesModel::Roles::Collapsible: {
auto idx = index.row() - 2;
return idx != spaceOrder_.lastChild(idx);
}
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::Hidden:
2022-04-20 03:49:37 +03:00
return hiddenTagIds_.contains("space:" + id);
2021-12-01 02:02:41 +03:00
case CommunitiesModel::Roles::Parent: {
if (auto p = spaceOrder_.parent(index.row() - 2); p >= 0)
return spaceOrder_.tree[p].id;
2021-12-01 02:02:41 +03:00
return "";
}
case CommunitiesModel::Roles::Depth:
return spaceOrder_.tree.at(index.row() - 2).depth;
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::Id:
return "space:" + id;
2022-07-15 17:19:06 +03:00
case CommunitiesModel::Roles::UnreadMessages: {
int count = 0;
auto end = spaceOrder_.lastChild(index.row() - 2);
for (int i = index.row() - 2; i <= end; i++)
count += spaceOrder_.tree[i].notificationCounts.notification_count;
return count;
}
case CommunitiesModel::Roles::HasLoudNotification: {
auto end = spaceOrder_.lastChild(index.row() - 2);
for (int i = index.row() - 2; i <= end; i++)
if (spaceOrder_.tree[i].notificationCounts.highlight_count > 0)
return true;
return false;
}
2021-09-18 01:22:33 +03:00
}
} else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) {
auto tag = tags_.at(index.row() - 2 - spaceOrder_.size());
if (tag == QLatin1String("m.favourite")) {
2021-09-18 01:22:33 +03:00
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QStringLiteral(":/icons/icons/ui/star.svg");
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::DisplayName:
return tr("Favourites");
case CommunitiesModel::Roles::Tooltip:
return tr("Rooms you have favourited.");
}
} else if (tag == QLatin1String("m.lowpriority")) {
2021-09-18 01:22:33 +03:00
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QStringLiteral(":/icons/icons/ui/lowprio.svg");
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::DisplayName:
return tr("Low Priority");
case CommunitiesModel::Roles::Tooltip:
return tr("Rooms with low priority.");
}
} else if (tag == QLatin1String("m.server_notice")) {
2021-09-18 01:22:33 +03:00
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QStringLiteral(":/icons/icons/ui/tag.svg");
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::DisplayName:
return tr("Server Notices");
case CommunitiesModel::Roles::Tooltip:
return tr("Messages from your server or administrator.");
}
} else {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QStringLiteral(":/icons/icons/ui/tag.svg");
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::DisplayName:
case CommunitiesModel::Roles::Tooltip:
return tag.mid(2);
}
}
2021-06-10 00:52:28 +03:00
2021-09-18 01:22:33 +03:00
switch (role) {
case CommunitiesModel::Roles::Hidden:
2022-04-20 03:49:37 +03:00
return hiddenTagIds_.contains("tag:" + tag);
2021-12-01 02:02:41 +03:00
case CommunitiesModel::Roles::Collapsed:
2021-09-18 01:22:33 +03:00
return true;
2021-12-01 02:02:41 +03:00
case CommunitiesModel::Roles::Collapsible:
return false;
case CommunitiesModel::Roles::Parent:
return "";
case CommunitiesModel::Roles::Depth:
return 0;
2021-09-18 01:22:33 +03:00
case CommunitiesModel::Roles::Id:
return "tag:" + tag;
2022-07-15 17:19:06 +03:00
case CommunitiesModel::Roles::UnreadMessages:
if (auto it = tagNotificationCache.find(tag); it != tagNotificationCache.end())
return (int)it->second.notification_count;
else
return 0;
2022-07-15 17:19:06 +03:00
case CommunitiesModel::Roles::HasLoudNotification:
if (auto it = tagNotificationCache.find(tag); it != tagNotificationCache.end())
return it->second.highlight_count > 0;
else
return 0;
2021-06-10 00:52:28 +03:00
}
2021-09-18 01:22:33 +03:00
}
return QVariant();
2021-06-10 00:52:28 +03:00
}
2021-12-01 02:02:41 +03:00
namespace {
struct temptree
{
std::map<std::string, temptree> children;
void insert(const std::vector<std::string> &parents, const std::string &child)
{
temptree *t = this;
for (const auto &e : parents)
t = &t->children[e];
t->children[child];
}
void flatten(CommunitiesModel::FlatTree &to, int i = 0) const
{
for (const auto &[child, subtree] : children) {
2022-07-16 04:53:13 +03:00
to.tree.push_back({QString::fromStdString(child), i, {}, false});
2021-12-01 02:02:41 +03:00
subtree.flatten(to, i + 1);
}
}
};
void
addChildren(temptree &t,
std::vector<std::string> path,
std::string child,
const std::map<std::string, std::set<std::string>> &children)
{
if (std::find(path.begin(), path.end(), child) != path.end())
return;
path.push_back(child);
if (children.count(child)) {
for (const auto &c : children.at(child)) {
t.insert(path, c);
addChildren(t, path, c, children);
}
}
}
}
2021-06-10 00:52:28 +03:00
void
CommunitiesModel::initializeSidebar()
{
2021-09-18 01:22:33 +03:00
beginResetModel();
tags_.clear();
2021-12-01 02:02:41 +03:00
spaceOrder_.tree.clear();
2021-09-18 01:22:33 +03:00
spaces_.clear();
tagNotificationCache.clear();
globalUnreads.notification_count = {};
dmUnreads.notification_count = {};
2022-07-16 04:53:13 +03:00
{
auto e = cache::client()->getAccountData(mtx::events::EventType::Direct);
if (e) {
if (auto event =
std::get_if<mtx::events::AccountDataEvent<mtx::events::account_data::Direct>>(
&e.value())) {
directMessages_.clear();
for (const auto &[userId, roomIds] : event->content.user_to_rooms)
for (const auto &roomId : roomIds)
directMessages_.push_back(roomId);
}
}
}
2021-09-18 01:22:33 +03:00
std::set<std::string> ts;
2021-12-01 02:02:41 +03:00
std::set<std::string> isSpace;
std::map<std::string, std::set<std::string>> spaceChilds;
std::map<std::string, std::set<std::string>> spaceParents;
2021-09-18 01:22:33 +03:00
auto infos = cache::roomInfo();
2022-07-16 04:49:41 +03:00
for (auto it = infos.begin(); it != infos.end(); ++it) {
2021-09-18 01:22:33 +03:00
if (it.value().is_space) {
spaces_[it.key()] = it.value();
2021-12-01 02:02:41 +03:00
isSpace.insert(it.key().toStdString());
2021-09-18 01:22:33 +03:00
} else {
for (const auto &t : it.value().tags) {
if (t.find("u.") == 0 || t.find("m." == 0)) {
ts.insert(t);
2021-06-10 00:52:28 +03:00
}
2021-09-18 01:22:33 +03:00
}
2021-06-10 00:52:28 +03:00
}
for (const auto &t : it->tags) {
auto tagId = QString::fromStdString(t);
auto &tNs = tagNotificationCache[tagId];
tNs.notification_count += it->notification_count;
tNs.highlight_count += it->highlight_count;
}
auto &e = roomNotificationCache[it.key()];
e.highlight_count = it->highlight_count;
e.notification_count = it->notification_count;
globalUnreads.notification_count += it->notification_count;
globalUnreads.highlight_count += it->highlight_count;
2022-07-16 04:49:41 +03:00
if (std::find(begin(directMessages_), end(directMessages_), it.key().toStdString()) !=
end(directMessages_)) {
dmUnreads.notification_count += it->notification_count;
dmUnreads.highlight_count += it->highlight_count;
}
2021-09-18 01:22:33 +03:00
}
2021-06-10 00:52:28 +03:00
2021-12-01 02:02:41 +03:00
// NOTE(Nico): We build a forrest from the Directed Cyclic(!) Graph of spaces. To do that we
// start with orphan spaces at the top. This leaves out some space circles, but there is no good
// way to break that cycle imo anyway. Then we carefully walk a tree down from each root in our
// forrest, carefully checking not to run in a circle and get lost forever.
// TODO(Nico): Optimize this. We can do this with a lot fewer allocations and checks.
for (const auto &space : isSpace) {
spaceParents[space];
for (const auto &p : cache::client()->getParentRoomIds(space)) {
spaceParents[space].insert(p);
spaceChilds[p].insert(space);
}
}
temptree spacetree;
std::vector<std::string> path;
for (const auto &space : isSpace) {
if (!spaceParents[space].empty())
continue;
spacetree.children[space] = {};
}
for (const auto &space : spacetree.children) {
addChildren(spacetree, path, space.first, spaceChilds);
}
// NOTE(Nico): This flattens the tree into a list, preserving the depth at each element.
spacetree.flatten(spaceOrder_);
2021-09-18 01:22:33 +03:00
for (const auto &t : ts)
tags_.push_back(QString::fromStdString(t));
2021-06-11 18:54:05 +03:00
2021-12-01 05:46:55 +03:00
spaceOrder_.restoreCollapsed();
for (auto &space : spaceOrder_.tree) {
for (const auto &c : cache::client()->getChildRoomIds(space.id.toStdString())) {
const auto &counts = roomNotificationCache[QString::fromStdString(c)];
space.notificationCounts.highlight_count += counts.highlight_count;
space.notificationCounts.notification_count += counts.notification_count;
}
}
2021-09-18 01:22:33 +03:00
endResetModel();
2021-06-10 00:52:28 +03:00
2021-09-18 01:22:33 +03:00
emit tagsChanged();
emit hiddenTagsChanged();
emit containsSubspacesChanged();
2021-06-10 00:52:28 +03:00
}
2021-12-01 05:46:55 +03:00
void
CommunitiesModel::FlatTree::storeCollapsed()
{
QList<QStringList> elements;
int depth = -1;
QStringList current;
elements.reserve(static_cast<int>(tree.size()));
2021-12-01 05:46:55 +03:00
for (const auto &e : tree) {
if (e.depth > depth) {
current.push_back(e.id);
2021-12-01 05:46:55 +03:00
} else if (e.depth == depth) {
current.back() = e.id;
2021-12-01 05:46:55 +03:00
} else {
current.pop_back();
current.back() = e.id;
2021-12-01 05:46:55 +03:00
}
if (e.collapsed)
elements.push_back(current);
}
UserSettings::instance()->setCollapsedSpaces(elements);
}
void
CommunitiesModel::FlatTree::restoreCollapsed()
{
QList<QStringList> elements = UserSettings::instance()->collapsedSpaces();
int depth = -1;
QStringList current;
for (auto &e : tree) {
if (e.depth > depth) {
current.push_back(e.id);
2021-12-01 05:46:55 +03:00
} else if (e.depth == depth) {
current.back() = e.id;
2021-12-01 05:46:55 +03:00
} else {
current.pop_back();
current.back() = e.id;
2021-12-01 05:46:55 +03:00
}
if (elements.contains(current))
e.collapsed = true;
}
}
2021-06-10 00:52:28 +03:00
void
CommunitiesModel::clear()
{
2021-09-18 01:22:33 +03:00
beginResetModel();
tags_.clear();
endResetModel();
resetCurrentTagId();
2021-06-10 00:52:28 +03:00
2021-09-18 01:22:33 +03:00
emit tagsChanged();
2021-06-10 00:52:28 +03:00
}
void
CommunitiesModel::sync(const mtx::responses::Sync &sync_)
2021-06-10 00:52:28 +03:00
{
2022-09-05 03:00:20 +03:00
bool tagsUpdated = false;
const auto userid = http::client()->user_id().to_string();
2021-09-18 01:22:33 +03:00
for (const auto &[roomid, room] : sync_.rooms.join) {
2021-09-18 01:22:33 +03:00
for (const auto &e : room.account_data.events)
if (std::holds_alternative<
mtx::events::AccountDataEvent<mtx::events::account_data::Tags>>(e)) {
tagsUpdated = true;
}
2022-09-05 03:00:20 +03:00
for (const auto &e : room.state.events) {
2021-09-18 01:22:33 +03:00
if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>(
e) ||
std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>(
2022-09-05 03:00:20 +03:00
e))
2021-09-18 01:22:33 +03:00
tagsUpdated = true;
2022-09-05 03:00:20 +03:00
if (auto ev = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(&e);
ev && ev->state_key == userid)
tagsUpdated = true;
}
for (const auto &e : room.timeline.events) {
2021-09-18 01:22:33 +03:00
if (std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Child>>(
e) ||
std::holds_alternative<mtx::events::StateEvent<mtx::events::state::space::Parent>>(
2022-09-05 03:00:20 +03:00
e))
2021-09-18 01:22:33 +03:00
tagsUpdated = true;
2022-09-05 03:00:20 +03:00
if (auto ev = std::get_if<mtx::events::StateEvent<mtx::events::state::Member>>(&e);
ev && ev->state_key == userid)
tagsUpdated = true;
}
2022-07-15 17:19:06 +03:00
auto roomId = QString::fromStdString(roomid);
auto &oldUnreads = roomNotificationCache[roomId];
auto notificationCDiff = -static_cast<int64_t>(oldUnreads.notification_count) +
static_cast<int64_t>(room.unread_notifications.notification_count);
auto highlightCDiff = -static_cast<int64_t>(oldUnreads.highlight_count) +
static_cast<int64_t>(room.unread_notifications.highlight_count);
auto applyDiff = [notificationCDiff,
highlightCDiff](mtx::responses::UnreadNotifications &n) {
n.highlight_count = static_cast<int64_t>(n.highlight_count) + highlightCDiff;
n.notification_count = static_cast<int64_t>(n.notification_count) + notificationCDiff;
};
2022-07-15 17:19:06 +03:00
if (highlightCDiff || notificationCDiff) {
// bool hidden = hiddenTagIds_.contains(roomId);
applyDiff(globalUnreads);
2022-07-15 17:19:06 +03:00
emit dataChanged(index(0),
index(0),
{
UnreadMessages,
HasLoudNotification,
});
if (std::find(begin(directMessages_), end(directMessages_), roomid) !=
end(directMessages_)) {
applyDiff(dmUnreads);
2022-07-15 17:19:06 +03:00
emit dataChanged(index(1),
index(1),
{
UnreadMessages,
HasLoudNotification,
});
}
auto spaces = cache::client()->getParentRoomIds(roomid);
auto tags = cache::singleRoomInfo(roomid).tags;
for (const auto &t : tags) {
auto tagId = QString::fromStdString(t);
applyDiff(tagNotificationCache[tagId]);
2022-07-15 17:19:06 +03:00
int idx = tags_.indexOf(tagId) + 2 + spaceOrder_.size();
emit dataChanged(index(idx),
index(idx),
{
UnreadMessages,
HasLoudNotification,
});
}
for (const auto &s : spaces) {
auto spaceId = QString::fromStdString(s);
for (int i = 0; i < spaceOrder_.size(); i++) {
if (spaceOrder_.tree[i].id != spaceId)
continue;
applyDiff(spaceOrder_.tree[i].notificationCounts);
2022-07-15 17:19:06 +03:00
int idx = i;
do {
emit dataChanged(index(idx + 2),
index(idx + 2),
{
UnreadMessages,
HasLoudNotification,
});
idx = spaceOrder_.parent(idx);
2022-07-15 17:19:06 +03:00
} while (idx != -1);
}
}
}
roomNotificationCache[roomId] = room.unread_notifications;
2021-09-18 01:22:33 +03:00
}
for (const auto &[roomid, room] : sync_.rooms.leave) {
2021-09-18 01:22:33 +03:00
(void)room;
2021-12-01 02:02:41 +03:00
if (spaces_.count(QString::fromStdString(roomid)))
2021-09-18 01:22:33 +03:00
tagsUpdated = true;
}
for (const auto &e : sync_.account_data.events) {
2022-04-26 03:54:40 +03:00
if (auto event =
std::get_if<mtx::events::AccountDataEvent<mtx::events::account_data::Direct>>(&e)) {
directMessages_.clear();
for (const auto &[userId, roomIds] : event->content.user_to_rooms)
for (const auto &roomId : roomIds)
directMessages_.push_back(roomId);
tagsUpdated = true;
break;
}
}
2021-09-18 01:22:33 +03:00
if (tagsUpdated)
initializeSidebar();
2021-06-10 00:52:28 +03:00
}
void
CommunitiesModel::setCurrentTagId(const QString &tagId)
2021-06-10 00:52:28 +03:00
{
if (tagId.startsWith(QLatin1String("tag:"))) {
2021-09-18 01:22:33 +03:00
auto tag = tagId.mid(4);
2021-12-29 00:30:12 +03:00
for (const auto &t : qAsConst(tags_)) {
2021-09-18 01:22:33 +03:00
if (t == tag) {
this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_);
return;
}
}
} else if (tagId.startsWith(QLatin1String("space:"))) {
2021-09-18 01:22:33 +03:00
auto tag = tagId.mid(6);
2021-12-01 02:02:41 +03:00
for (const auto &t : spaceOrder_.tree) {
if (t.id == tag) {
2021-09-18 01:22:33 +03:00
this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_);
return;
}
2021-06-10 00:52:28 +03:00
}
} else if (tagId == QLatin1String("dm")) {
this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_);
return;
2021-09-18 01:22:33 +03:00
}
2021-06-10 00:52:28 +03:00
this->currentTagId_ = QLatin1String("");
2021-09-18 01:22:33 +03:00
emit currentTagIdChanged(currentTagId_);
2021-06-10 00:52:28 +03:00
}
2021-06-11 18:54:05 +03:00
void
CommunitiesModel::toggleTagId(QString tagId)
{
if (hiddenTagIds_.contains(tagId))
2022-04-20 03:49:37 +03:00
hiddenTagIds_.removeOne(tagId);
else
2022-04-20 03:49:37 +03:00
hiddenTagIds_.push_back(tagId);
UserSettings::instance()->setHiddenTags(hiddenTagIds_);
2021-09-18 01:22:33 +03:00
if (tagId.startsWith(QLatin1String("tag:"))) {
2021-09-18 01:22:33 +03:00
auto idx = tags_.indexOf(tagId.mid(4));
if (idx != -1)
emit dataChanged(
index(idx + 1 + spaceOrder_.size()), index(idx + 1 + spaceOrder_.size()), {Hidden});
} else if (tagId.startsWith(QLatin1String("space:"))) {
2021-09-18 01:22:33 +03:00
auto idx = spaceOrder_.indexOf(tagId.mid(6));
if (idx != -1)
emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
} else if (tagId == QLatin1String("dm")) {
emit dataChanged(index(1), index(1), {Hidden});
2021-09-18 01:22:33 +03:00
}
emit hiddenTagsChanged();
2021-06-11 18:54:05 +03:00
}
2021-12-01 02:02:41 +03:00
void
CommunitiesModel::toggleTagMute(QString tagId)
{
if (tagId.isEmpty())
tagId = QStringLiteral("global");
if (mutedTagIds_.contains(tagId))
mutedTagIds_.removeOne(tagId);
else
mutedTagIds_.push_back(tagId);
UserSettings::instance()->setMutedTags(mutedTagIds_);
if (tagId.startsWith(QLatin1String("tag:"))) {
auto idx = tags_.indexOf(tagId.mid(4));
if (idx != -1)
2022-07-16 13:03:03 +03:00
emit dataChanged(index(idx + 2 + spaceOrder_.size()),
index(idx + 2 + spaceOrder_.size()));
} else if (tagId.startsWith(QLatin1String("space:"))) {
auto idx = spaceOrder_.indexOf(tagId.mid(6));
if (idx != -1)
2022-07-16 13:03:03 +03:00
emit dataChanged(index(idx + 2), index(idx + 2));
} else if (tagId == QLatin1String("dm")) {
emit dataChanged(index(1), index(1));
} else if (tagId == QLatin1String("global")) {
emit dataChanged(index(0), index(0));
}
}
2021-12-01 02:02:41 +03:00
FilteredCommunitiesModel::FilteredCommunitiesModel(CommunitiesModel *model, QObject *parent)
: QSortFilterProxyModel(parent)
{
setSourceModel(model);
setDynamicSortFilter(true);
sort(0);
}
namespace {
enum Categories
{
World,
Direct,
Favourites,
Server,
LowPrio,
Space,
UserTag,
};
Categories
tagIdToCat(QString tagId)
{
if (tagId.isEmpty())
return World;
else if (tagId == QLatin1String("dm"))
2021-12-01 02:02:41 +03:00
return Direct;
else if (tagId == QLatin1String("tag:m.favourite"))
2021-12-01 02:02:41 +03:00
return Favourites;
else if (tagId == QLatin1String("tag:m.server_notice"))
2021-12-01 02:02:41 +03:00
return Server;
else if (tagId == QLatin1String("tag:m.lowpriority"))
2021-12-01 02:02:41 +03:00
return LowPrio;
else if (tagId.startsWith(QLatin1String("space:")))
2021-12-01 02:02:41 +03:00
return Space;
else
return UserTag;
}
}
bool
FilteredCommunitiesModel::lessThan(const QModelIndex &left, const QModelIndex &right) const
{
QModelIndex const left_idx = sourceModel()->index(left.row(), 0, QModelIndex());
QModelIndex const right_idx = sourceModel()->index(right.row(), 0, QModelIndex());
Categories leftCat = tagIdToCat(sourceModel()->data(left_idx, CommunitiesModel::Id).toString());
Categories rightCat =
tagIdToCat(sourceModel()->data(right_idx, CommunitiesModel::Id).toString());
if (leftCat != rightCat)
return leftCat < rightCat;
if (leftCat == Space) {
return left.row() < right.row();
}
QString leftName = sourceModel()->data(left_idx, CommunitiesModel::DisplayName).toString();
QString rightName = sourceModel()->data(right_idx, CommunitiesModel::DisplayName).toString();
return leftName.compare(rightName, Qt::CaseInsensitive) < 0;
}
bool
FilteredCommunitiesModel::filterAcceptsRow(int sourceRow, const QModelIndex &) const
{
CommunitiesModel *m = qobject_cast<CommunitiesModel *>(this->sourceModel());
if (!m)
return true;
if (sourceRow < 2 || sourceRow - 2 >= m->spaceOrder_.size())
return true;
auto idx = sourceRow - 2;
while (idx >= 0 && m->spaceOrder_.tree[idx].depth > 0) {
idx = m->spaceOrder_.parent(idx);
if (idx >= 0 && m->spaceOrder_.tree.at(idx).collapsed)
return false;
}
return true;
}
2022-08-10 01:20:44 +03:00
QVariantList
CommunitiesModel::spaceChildrenListFromIndex(QString room, int idx) const
{
if (idx < -1)
return {};
auto room_ = room.toStdString();
int begin = idx + 1;
int end = idx >= 0 ? this->spaceOrder_.lastChild(idx) + 1 : this->spaceOrder_.size();
QVariantList ret;
bool canSendParent = Permissions(room).canChange(qml_mtx_events::SpaceParent);
for (int i = begin; i < end; i++) {
const auto &e = spaceOrder_.tree[i];
if (e.depth == spaceOrder_.tree[begin].depth && spaces_.count(e.id)) {
bool canSendChild = Permissions(e.id).canChange(qml_mtx_events::SpaceChild);
2022-08-10 01:28:23 +03:00
// For now hide the space, if we can't send any child, since then the only allowed
// action would be removing a space and even that only works if it currently only has a
// parent set in the child.
if (!canSendChild)
continue;
auto spaceId = e.id.toStdString();
2022-08-10 01:20:44 +03:00
auto child =
cache::client()->getStateEvent<mtx::events::state::space::Child>(spaceId, room_);
auto parent =
cache::client()->getStateEvent<mtx::events::state::space::Parent>(room_, spaceId);
bool childValid =
child && !child->content.via.value_or(std::vector<std::string>{}).empty();
bool parentValid =
parent && !parent->content.via.value_or(std::vector<std::string>{}).empty();
bool canonical = parent && parent->content.canonical;
if (e.id == room) {
canonical = parentValid = childValid = canSendChild = canSendParent = false;
}
ret.push_back(
QVariant::fromValue(SpaceItem(e.id,
QString::fromStdString(spaces_.at(e.id).name),
i,
childValid,
parentValid,
canonical,
canSendChild,
canSendParent)));
}
}
nhlog::ui()->critical("Returning {} spaces", ret.size());
return ret;
}
void
CommunitiesModel::updateSpaceStatus(QString space,
QString room,
bool setParent,
bool setChild,
bool canonical) const
{
nhlog::ui()->critical("Setting space {} children {}: {} {} {}",
space.toStdString(),
room.toStdString(),
setParent,
setChild,
canonical);
auto child =
cache::client()
->getStateEvent<mtx::events::state::space::Child>(space.toStdString(), room.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::space::Child>{})
.content;
auto parent =
cache::client()
->getStateEvent<mtx::events::state::space::Parent>(room.toStdString(), space.toStdString())
.value_or(mtx::events::StateEvent<mtx::events::state::space::Parent>{})
.content;
if (setChild) {
if (!child.via || child.via->empty()) {
child.via = utils::roomVias(room.toStdString());
child.suggested = true;
http::client()->send_state_event(
space.toStdString(),
room.toStdString(),
child,
[space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
if (err) {
ChatPage::instance()->showNotification(
tr("Failed to update space child: %1")
.arg(QString::fromStdString(err->matrix_error.error)));
nhlog::net()->error("Failed to update child {} of {}: {}",
room.toStdString(),
space.toStdString());
}
});
}
} else {
if (child.via && !child.via->empty()) {
http::client()->send_state_event(
space.toStdString(),
room.toStdString(),
mtx::events::state::space::Child{},
[space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
if (err) {
ChatPage::instance()->showNotification(
tr("Failed to delete space child: %1")
.arg(QString::fromStdString(err->matrix_error.error)));
nhlog::net()->error("Failed to delete child {} of {}: {}",
room.toStdString(),
space.toStdString());
}
});
}
}
if (setParent) {
if (!parent.via || parent.via->empty() || canonical != parent.canonical) {
parent.via = utils::roomVias(room.toStdString());
parent.canonical = canonical;
http::client()->send_state_event(
room.toStdString(),
space.toStdString(),
parent,
[space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
if (err) {
ChatPage::instance()->showNotification(
tr("Failed to update space parent: %1")
.arg(QString::fromStdString(err->matrix_error.error)));
nhlog::net()->error("Failed to update parent {} of {}: {}",
space.toStdString(),
room.toStdString());
}
});
}
} else {
if (parent.via && !parent.via->empty()) {
http::client()->send_state_event(
room.toStdString(),
space.toStdString(),
mtx::events::state::space::Parent{},
[space, room](mtx::responses::EventId, mtx::http::RequestErr err) {
if (err) {
ChatPage::instance()->showNotification(
tr("Failed to delete space parent: %1")
.arg(QString::fromStdString(err->matrix_error.error)));
nhlog::net()->error("Failed to delete parent {} of {}: {}",
space.toStdString(),
room.toStdString());
}
});
}
}
}