Skip to content
This repository has been archived by the owner on Oct 17, 2019. It is now read-only.

Display tags as sorting items in the community panel #401

Merged
merged 7 commits into from
Sep 28, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added resources/icons/ui/lowprio.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/icons/ui/lowprio@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/icons/ui/star.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/icons/ui/star@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/icons/ui/tag.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added resources/icons/ui/tag@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
7 changes: 7 additions & 0 deletions resources/res.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
52 changes: 52 additions & 0 deletions src/Cache.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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()));

Expand Down Expand Up @@ -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)
{
Expand Down
13 changes: 13 additions & 0 deletions src/Cache.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -384,11 +392,16 @@ class Cache : public QObject

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.
Expand Down
6 changes: 6 additions & 0 deletions src/ChatPage.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -1079,6 +1081,9 @@ ChatPage::trySync()
emit syncTopBar(updates);
emit syncRoomlist(updates);

auto tag_updates = cache::client()->roomTagUpdates(res);
emit syncTags(tag_updates);
elinorbgr marked this conversation as resolved.
Show resolved Hide resolved

cache::client()->deleteOldData();
} catch (const lmdb::map_full_error &e) {
nhlog::db()->error("lmdb is full: {}", e.what());
Expand Down Expand Up @@ -1213,6 +1218,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();
Expand Down
1 change: 1 addition & 0 deletions src/ChatPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ public slots:
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);

Expand Down
100 changes: 99 additions & 1 deletion src/CommunitiesList.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand All @@ -56,6 +64,59 @@ 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, "m."))
elinorbgr marked this conversation as resolved.
Show resolved Hide resolved
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()) {
elinorbgr marked this conversation as resolved.
Show resolved Hide resolved
// 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() - 4).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
Expand Down Expand Up @@ -193,3 +254,40 @@ 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);
}

contentsLayout_->insertWidget(contentsLayout_->count() - 1, communities_["world"].data());
for (auto item : header)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a member function or a lambda for those calls.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you referring to the fact that contentsLayout_->insertWidget(contentsLayout_->count() - 1, item); is a long call and that it implies a lot of repetition?

I realized I could simply remove the final "Stretch" item and insert it back in the layout at the end, rather than insert all widgets "one before the end". Would that be okay too?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I was referring to that repetition. I'm fine with any solution that doesn't have a lot of duplication.

contentsLayout_->insertWidget(contentsLayout_->count() - 1, item);
for (auto item : communities)
contentsLayout_->insertWidget(contentsLayout_->count() - 1, item);
for (auto item : tags)
contentsLayout_->insertWidget(contentsLayout_->count() - 1, item);
for (auto item : footer)
contentsLayout_->insertWidget(contentsLayout_->count() - 1, item);
}
5 changes: 5 additions & 0 deletions src/CommunitiesList.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <QSharedPointer>
#include <QVBoxLayout>

#include "Cache.h"
#include "CommunitiesListItem.h"
#include "ui/Theme.h"

Expand All @@ -20,6 +21,9 @@ class CommunitiesList : public QWidget
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);
Expand All @@ -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
Expand Down
39 changes: 38 additions & 1 deletion src/CommunitiesListItem.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -98,11 +113,33 @@ CommunitiesListItem::resolveName() const
{
if (!name_.isEmpty())
return name_;

if (groupId_.startsWith("tag:"))
return groupId_.right(groupId_.size() - 4);
if (!groupId_.startsWith("+"))
return QString("Group"); // Group with no name or id.

// Extract the localpart of the group.
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() - 4);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to have this 4 number derived from the tag: string somehow. Currently it seems like a magic number if you only read a part of the code.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is adding a comment explaining it enough, or should I replace it by strlen("tag:")?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've seen it in 2-3 places so maybe it would be better to use something like strlen.

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