mirror of
https://github.com/Nheko-Reborn/nheko.git
synced 2024-11-25 04:28:49 +03:00
Display tags as sorting items in the community panel (#401)
This commit is contained in:
parent
59a1b6b47c
commit
18a98a7c1d
15 changed files with 234 additions and 3 deletions
BIN
resources/icons/ui/lowprio.png
Normal file
BIN
resources/icons/ui/lowprio.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 395 B |
BIN
resources/icons/ui/lowprio@2x.png
Normal file
BIN
resources/icons/ui/lowprio@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 779 B |
BIN
resources/icons/ui/star.png
Normal file
BIN
resources/icons/ui/star.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 475 B |
BIN
resources/icons/ui/star@2x.png
Normal file
BIN
resources/icons/ui/star@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 841 B |
BIN
resources/icons/ui/tag.png
Normal file
BIN
resources/icons/ui/tag.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 477 B |
BIN
resources/icons/ui/tag@2x.png
Normal file
BIN
resources/icons/ui/tag@2x.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1,004 B |
|
@ -53,6 +53,13 @@
|
|||
<file>icons/ui/world.png</file>
|
||||
<file>icons/ui/world@2x.png</file>
|
||||
|
||||
<file>icons/ui/tag.png</file>
|
||||
<file>icons/ui/tag@2x.png</file>
|
||||
<file>icons/ui/star.png</file>
|
||||
<file>icons/ui/star@2x.png</file>
|
||||
<file>icons/ui/lowprio.png</file>
|
||||
<file>icons/ui/lowprio@2x.png</file>
|
||||
|
||||
<file>icons/ui/edit.png</file>
|
||||
<file>icons/ui/edit@2x.png</file>
|
||||
|
||||
|
|
|
@ -936,6 +936,8 @@ Cache::calculateRoomReadStatus(const std::string &room_id)
|
|||
void
|
||||
Cache::saveState(const mtx::responses::Sync &res)
|
||||
{
|
||||
using namespace mtx::events;
|
||||
|
||||
auto txn = lmdb::txn::begin(env_);
|
||||
|
||||
setNextBatchToken(txn, res.next_batch);
|
||||
|
@ -957,6 +959,35 @@ Cache::saveState(const mtx::responses::Sync &res)
|
|||
getRoomAvatarUrl(txn, statesdb, membersdb, QString::fromStdString(room.first))
|
||||
.toStdString();
|
||||
|
||||
// Process the account_data associated with this room
|
||||
bool has_new_tags = false;
|
||||
for (const auto &evt : room.second.account_data.events) {
|
||||
// for now only fetch tag events
|
||||
if (evt.type() == typeid(Event<account_data::Tag>)) {
|
||||
auto tags_evt = boost::get<Event<account_data::Tag>>(evt);
|
||||
has_new_tags = true;
|
||||
for (const auto &tag : tags_evt.content.tags) {
|
||||
updatedInfo.tags.push_back(tag.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!has_new_tags) {
|
||||
// retrieve the old tags, they haven't changed
|
||||
lmdb::val data;
|
||||
if (lmdb::dbi_get(txn, roomsDb_, lmdb::val(room.first), data)) {
|
||||
try {
|
||||
RoomInfo tmp =
|
||||
json::parse(std::string(data.data(), data.size()));
|
||||
updatedInfo.tags = tmp.tags;
|
||||
} catch (const json::exception &e) {
|
||||
nhlog::db()->warn(
|
||||
"failed to parse room info: room_id ({}), {}",
|
||||
room.first,
|
||||
std::string(data.data(), data.size()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lmdb::dbi_put(
|
||||
txn, roomsDb_, lmdb::val(room.first), lmdb::val(json(updatedInfo).dump()));
|
||||
|
||||
|
@ -1078,6 +1109,27 @@ Cache::roomsWithStateUpdates(const mtx::responses::Sync &res)
|
|||
return rooms;
|
||||
}
|
||||
|
||||
std::vector<std::string>
|
||||
Cache::roomsWithTagUpdates(const mtx::responses::Sync &res)
|
||||
{
|
||||
using namespace mtx::events;
|
||||
|
||||
std::vector<std::string> rooms;
|
||||
for (const auto &room : res.rooms.join) {
|
||||
bool hasUpdates = false;
|
||||
for (const auto &evt : room.second.account_data.events) {
|
||||
if (evt.type() == typeid(Event<account_data::Tag>)) {
|
||||
hasUpdates = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasUpdates)
|
||||
rooms.emplace_back(room.first);
|
||||
}
|
||||
|
||||
return rooms;
|
||||
}
|
||||
|
||||
RoomInfo
|
||||
Cache::singleRoomInfo(const std::string &room_id)
|
||||
{
|
||||
|
|
13
src/Cache.h
13
src/Cache.h
|
@ -115,6 +115,8 @@ struct RoomInfo
|
|||
bool guest_access = false;
|
||||
//! Metadata describing the last message in the timeline.
|
||||
DescInfo msgInfo;
|
||||
//! The list of tags associated with this room
|
||||
std::vector<std::string> tags;
|
||||
};
|
||||
|
||||
inline void
|
||||
|
@ -129,6 +131,9 @@ to_json(json &j, const RoomInfo &info)
|
|||
|
||||
if (info.member_count != 0)
|
||||
j["member_count"] = info.member_count;
|
||||
|
||||
if (info.tags.size() != 0)
|
||||
j["tags"] = info.tags;
|
||||
}
|
||||
|
||||
inline void
|
||||
|
@ -143,6 +148,9 @@ from_json(const json &j, RoomInfo &info)
|
|||
|
||||
if (j.count("member_count"))
|
||||
info.member_count = j.at("member_count");
|
||||
|
||||
if (j.count("tags"))
|
||||
info.tags = j.at("tags").get<std::vector<std::string>>();
|
||||
}
|
||||
|
||||
//! Basic information per member;
|
||||
|
@ -384,11 +392,16 @@ public:
|
|||
|
||||
RoomInfo singleRoomInfo(const std::string &room_id);
|
||||
std::vector<std::string> roomsWithStateUpdates(const mtx::responses::Sync &res);
|
||||
std::vector<std::string> roomsWithTagUpdates(const mtx::responses::Sync &res);
|
||||
std::map<QString, RoomInfo> getRoomInfo(const std::vector<std::string> &rooms);
|
||||
std::map<QString, RoomInfo> roomUpdates(const mtx::responses::Sync &sync)
|
||||
{
|
||||
return getRoomInfo(roomsWithStateUpdates(sync));
|
||||
}
|
||||
std::map<QString, RoomInfo> roomTagUpdates(const mtx::responses::Sync &sync)
|
||||
{
|
||||
return getRoomInfo(roomsWithTagUpdates(sync));
|
||||
}
|
||||
|
||||
//! Calculates which the read status of a room.
|
||||
//! Whether all the events in the timeline have been read.
|
||||
|
|
|
@ -569,6 +569,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
|
|||
});
|
||||
});
|
||||
connect(this, &ChatPage::syncRoomlist, room_list_, &RoomList::sync);
|
||||
connect(this, &ChatPage::syncTags, communitiesList_, &CommunitiesList::syncTags);
|
||||
connect(
|
||||
this, &ChatPage::syncTopBar, this, [this](const std::map<QString, RoomInfo> &updates) {
|
||||
if (updates.find(currentRoom()) != updates.end())
|
||||
|
@ -797,6 +798,7 @@ ChatPage::loadStateFromCache()
|
|||
|
||||
emit initializeEmptyViews(cache::client()->roomMessages());
|
||||
emit initializeRoomList(cache::client()->roomInfo());
|
||||
emit syncTags(cache::client()->roomInfo().toStdMap());
|
||||
|
||||
cache::client()->calculateRoomReadStatus();
|
||||
|
||||
|
@ -1079,6 +1081,8 @@ ChatPage::trySync()
|
|||
emit syncTopBar(updates);
|
||||
emit syncRoomlist(updates);
|
||||
|
||||
emit syncTags(cache::client()->roomTagUpdates(res));
|
||||
|
||||
cache::client()->deleteOldData();
|
||||
} catch (const lmdb::map_full_error &e) {
|
||||
nhlog::db()->error("lmdb is full: {}", e.what());
|
||||
|
@ -1213,6 +1217,7 @@ ChatPage::initialSyncHandler(const mtx::responses::Sync &res, mtx::http::Request
|
|||
emit initializeRoomList(cache::client()->roomInfo());
|
||||
|
||||
cache::client()->calculateRoomReadStatus();
|
||||
emit syncTags(cache::client()->roomInfo().toStdMap());
|
||||
} catch (const lmdb::error &e) {
|
||||
nhlog::db()->error("failed to save state after initial sync: {}", e.what());
|
||||
startInitialSync();
|
||||
|
|
|
@ -136,6 +136,7 @@ signals:
|
|||
void initializeEmptyViews(const std::map<QString, mtx::responses::Timeline> &msgs);
|
||||
void syncUI(const mtx::responses::Rooms &rooms);
|
||||
void syncRoomlist(const std::map<QString, RoomInfo> &updates);
|
||||
void syncTags(const std::map<QString, RoomInfo> &updates);
|
||||
void syncTopBar(const std::map<QString, RoomInfo> &updates);
|
||||
void dropToLoginPageCb(const QString &msg);
|
||||
|
||||
|
|
|
@ -47,7 +47,15 @@ CommunitiesList::CommunitiesList(QWidget *parent)
|
|||
void
|
||||
CommunitiesList::setCommunities(const mtx::responses::JoinedGroups &response)
|
||||
{
|
||||
communities_.clear();
|
||||
// remove all non-tag communities
|
||||
auto it = communities_.begin();
|
||||
while (it != communities_.end()) {
|
||||
if (it->second->is_tag()) {
|
||||
++it;
|
||||
} else {
|
||||
it = communities_.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
addGlobalItem();
|
||||
|
||||
|
@ -56,6 +64,60 @@ CommunitiesList::setCommunities(const mtx::responses::JoinedGroups &response)
|
|||
|
||||
communities_["world"]->setPressedState(true);
|
||||
emit communityChanged("world");
|
||||
sortEntries();
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesList::syncTags(const std::map<QString, RoomInfo> &info)
|
||||
{
|
||||
for (const auto &room : info)
|
||||
setTagsForRoom(room.first, room.second.tags);
|
||||
sortEntries();
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesList::setTagsForRoom(const QString &room_id, const std::vector<std::string> &tags)
|
||||
{
|
||||
// create missing tag if any
|
||||
for (const auto &tag : tags) {
|
||||
// filter out tags we should ignore according to the spec
|
||||
// https://matrix.org/docs/spec/client_server/r0.4.0.html#id154
|
||||
// nheko currently does not make use of internal tags
|
||||
// so we ignore any tag containig a `.` (which would indicate a tag
|
||||
// in the form `tld.domain.*`) except for `m.*` and `u.*`.
|
||||
if (tag.find(".") != ::std::string::npos && tag.compare(0, 2, "m.") &&
|
||||
tag.compare(0, 2, "u."))
|
||||
continue;
|
||||
QString name = QString("tag:") + QString::fromStdString(tag);
|
||||
if (!communityExists(name)) {
|
||||
addCommunity(std::string("tag:") + tag);
|
||||
}
|
||||
}
|
||||
// update membership of the room for all tags
|
||||
auto it = communities_.begin();
|
||||
while (it != communities_.end()) {
|
||||
// Skip if the community is not a tag
|
||||
if (!it->second->is_tag()) {
|
||||
++it;
|
||||
continue;
|
||||
}
|
||||
// insert or remove the room from the tag as appropriate
|
||||
std::string current_tag =
|
||||
it->first.right(it->first.size() - strlen("tag:")).toStdString();
|
||||
if (std::find(tags.begin(), tags.end(), current_tag) != tags.end()) {
|
||||
// the room has this tag
|
||||
it->second->addRoom(room_id);
|
||||
} else {
|
||||
// the room does not have this tag
|
||||
it->second->delRoom(room_id);
|
||||
}
|
||||
// Check if the tag is now empty, if yes delete it
|
||||
if (it->second->rooms().empty()) {
|
||||
it = communities_.erase(it);
|
||||
} else {
|
||||
++it;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -193,3 +255,47 @@ CommunitiesList::roomList(const QString &id) const
|
|||
|
||||
return {};
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesList::sortEntries()
|
||||
{
|
||||
std::vector<CommunitiesListItem *> header;
|
||||
std::vector<CommunitiesListItem *> communities;
|
||||
std::vector<CommunitiesListItem *> tags;
|
||||
std::vector<CommunitiesListItem *> footer;
|
||||
// remove all the contents and sort them in the 4 vectors
|
||||
for (auto &entry : communities_) {
|
||||
CommunitiesListItem *item = entry.second.data();
|
||||
contentsLayout_->removeWidget(item);
|
||||
// world is handled separately
|
||||
if (entry.first == "world")
|
||||
continue;
|
||||
// sort the rest
|
||||
if (item->is_tag())
|
||||
if (entry.first == "tag:m.favourite")
|
||||
header.push_back(item);
|
||||
else if (entry.first == "tag:m.lowpriority")
|
||||
footer.push_back(item);
|
||||
else
|
||||
tags.push_back(item);
|
||||
else
|
||||
communities.push_back(item);
|
||||
}
|
||||
|
||||
// now there remains only the stretch in the layout, remove it
|
||||
QLayoutItem *stretch = contentsLayout_->itemAt(0);
|
||||
contentsLayout_->removeItem(stretch);
|
||||
|
||||
contentsLayout_->addWidget(communities_["world"].data());
|
||||
|
||||
auto insert_widgets = [this](auto &vec) {
|
||||
for (auto item : vec)
|
||||
contentsLayout_->addWidget(item);
|
||||
};
|
||||
insert_widgets(header);
|
||||
insert_widgets(communities);
|
||||
insert_widgets(tags);
|
||||
insert_widgets(footer);
|
||||
|
||||
contentsLayout_->addItem(stretch);
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
#include <QSharedPointer>
|
||||
#include <QVBoxLayout>
|
||||
|
||||
#include "Cache.h"
|
||||
#include "CommunitiesListItem.h"
|
||||
#include "ui/Theme.h"
|
||||
|
||||
|
@ -20,6 +21,9 @@ public:
|
|||
void removeCommunity(const QString &id) { communities_.erase(id); };
|
||||
std::map<QString, bool> roomList(const QString &id) const;
|
||||
|
||||
void syncTags(const std::map<QString, RoomInfo> &info);
|
||||
void setTagsForRoom(const QString &id, const std::vector<std::string> &tags);
|
||||
|
||||
signals:
|
||||
void communityChanged(const QString &id);
|
||||
void avatarRetrieved(const QString &id, const QPixmap &img);
|
||||
|
@ -34,6 +38,7 @@ public slots:
|
|||
private:
|
||||
void fetchCommunityAvatar(const QString &id, const QString &avatarUrl);
|
||||
void addGlobalItem() { addCommunity("world"); }
|
||||
void sortEntries();
|
||||
|
||||
//! Check whether or not a community id is currently managed.
|
||||
bool communityExists(const QString &id) const
|
||||
|
|
|
@ -19,6 +19,21 @@ CommunitiesListItem::CommunitiesListItem(QString group_id, QWidget *parent)
|
|||
|
||||
if (groupId_ == "world")
|
||||
avatar_ = QPixmap(":/icons/icons/ui/world.png");
|
||||
else if (groupId_ == "tag:m.favourite")
|
||||
avatar_ = QPixmap(":/icons/icons/ui/star.png");
|
||||
else if (groupId_ == "tag:m.lowpriority")
|
||||
avatar_ = QPixmap(":/icons/icons/ui/lowprio.png");
|
||||
else if (groupId_.startsWith("tag:"))
|
||||
avatar_ = QPixmap(":/icons/icons/ui/tag.png");
|
||||
|
||||
updateTooltip();
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesListItem::setName(QString name)
|
||||
{
|
||||
name_ = name;
|
||||
updateTooltip();
|
||||
}
|
||||
|
||||
void
|
||||
|
@ -98,7 +113,8 @@ CommunitiesListItem::resolveName() const
|
|||
{
|
||||
if (!name_.isEmpty())
|
||||
return name_;
|
||||
|
||||
if (groupId_.startsWith("tag:"))
|
||||
return groupId_.right(groupId_.size() - strlen("tag:"));
|
||||
if (!groupId_.startsWith("+"))
|
||||
return QString("Group"); // Group with no name or id.
|
||||
|
||||
|
@ -106,3 +122,24 @@ CommunitiesListItem::resolveName() const
|
|||
auto firstPart = groupId_.split(':').at(0);
|
||||
return firstPart.right(firstPart.size() - 1);
|
||||
}
|
||||
|
||||
void
|
||||
CommunitiesListItem::updateTooltip()
|
||||
{
|
||||
if (groupId_ == "world")
|
||||
setToolTip(tr("All rooms"));
|
||||
else if (is_tag()) {
|
||||
QString tag = groupId_.right(groupId_.size() - strlen("tag:"));
|
||||
if (tag == "m.favourite")
|
||||
setToolTip(tr("Favourite rooms"));
|
||||
else if (tag == "m.lowpriority")
|
||||
setToolTip(tr("Low priority rooms"));
|
||||
else if (tag.startsWith("u."))
|
||||
setToolTip(tag.right(tag.size() - 2) + tr(" (tag)"));
|
||||
else
|
||||
setToolTip(tag + tr(" (tag)"));
|
||||
} else {
|
||||
QString name = resolveName();
|
||||
setToolTip(name + tr(" (community)"));
|
||||
}
|
||||
}
|
|
@ -28,13 +28,17 @@ class CommunitiesListItem : public QWidget
|
|||
public:
|
||||
CommunitiesListItem(QString group_id, QWidget *parent = nullptr);
|
||||
|
||||
void setName(QString name) { name_ = name; }
|
||||
void setName(QString name);
|
||||
bool isPressed() const { return isPressed_; }
|
||||
void setAvatar(const QImage &img);
|
||||
|
||||
void setRooms(std::map<QString, bool> room_ids) { room_ids_ = std::move(room_ids); }
|
||||
void addRoom(const QString &id) { room_ids_[id] = true; }
|
||||
void delRoom(const QString &id) { room_ids_.erase(id); }
|
||||
std::map<QString, bool> rooms() const { return room_ids_; }
|
||||
|
||||
bool is_tag() const { return groupId_.startsWith("tag:"); }
|
||||
|
||||
QColor highlightedBackgroundColor() const { return highlightedBackgroundColor_; }
|
||||
QColor hoverBackgroundColor() const { return hoverBackgroundColor_; }
|
||||
QColor backgroundColor() const { return backgroundColor_; }
|
||||
|
@ -68,6 +72,7 @@ private:
|
|||
const int IconSize = 36;
|
||||
|
||||
QString resolveName() const;
|
||||
void updateTooltip();
|
||||
|
||||
std::map<QString, bool> room_ids_;
|
||||
|
||||
|
|
Loading…
Reference in a new issue