Display tags as sorting items in the community panel (#401)

This commit is contained in:
Victor Berger 2018-09-28 14:40:51 +02:00 committed by mujx
parent 59a1b6b47c
commit 18a98a7c1d
15 changed files with 234 additions and 3 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 395 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 779 B

BIN
resources/icons/ui/star.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 475 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 841 B

BIN
resources/icons/ui/tag.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 477 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1004 B

View File

@ -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>

View 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)
{

View File

@ -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.

View File

@ -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();

View File

@ -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);

View File

@ -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);
}

View File

@ -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

View File

@ -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)"));
}
}

View File

@ -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_;