Merge pull request #822 from Nheko-Reborn/dm-filter

Add a filter for direct chats
This commit is contained in:
DeepBlueV7.X 2021-11-21 22:46:51 +01:00 committed by GitHub
commit b1092c0f2f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 412 additions and 60 deletions

View file

@ -413,7 +413,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare( FetchContent_Declare(
MatrixClient MatrixClient
GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git GIT_REPOSITORY https://github.com/Nheko-Reborn/mtxclient.git
GIT_TAG v0.6.0 GIT_TAG ffc1d3e13a507fa501966b2d7e9d4eda881f6bf4
) )
set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "") set(BUILD_LIB_EXAMPLES OFF CACHE INTERNAL "")
set(BUILD_LIB_TESTS OFF CACHE INTERNAL "") set(BUILD_LIB_TESTS OFF CACHE INTERNAL "")
@ -644,7 +644,7 @@ endif()
if(WIN32) if(WIN32)
add_executable (nheko WIN32 ${OS_BUNDLE} ${NHEKO_DEPS}) add_executable (nheko WIN32 ${OS_BUNDLE} ${NHEKO_DEPS})
target_compile_definitions(nheko PRIVATE _WIN32_WINNT=0x0601) target_compile_definitions(nheko PRIVATE _WIN32_WINNT=0x0601 NOMINMAX WIN32_LEAN_AND_MEAN STRICT)
else() else()
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS}) add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
@ -710,7 +710,7 @@ if(USE_BUNDLED_COEURL)
FetchContent_Declare( FetchContent_Declare(
coeurl coeurl
GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git
GIT_TAG v0.1.0 GIT_TAG abafd60d7e9f5cce76c9abad3b2b3dc1382e5349
) )
FetchContent_MakeAvailable(coeurl) FetchContent_MakeAvailable(coeurl)
target_link_libraries(nheko PUBLIC coeurl::coeurl) target_link_libraries(nheko PUBLIC coeurl::coeurl)

View file

@ -175,8 +175,7 @@ modules:
- -Ddefault_library=static - -Ddefault_library=static
name: coeurl name: coeurl
sources: sources:
- commit: a08f619adaa1ccd34eb6315d6578eddae0d1cc9b - commit: abafd60d7e9f5cce76c9abad3b2b3dc1382e5349
tag: v0.1.0
type: git type: git
url: https://nheko.im/nheko-reborn/coeurl.git url: https://nheko.im/nheko-reborn/coeurl.git
- config-opts: - config-opts:
@ -187,8 +186,7 @@ modules:
buildsystem: cmake-ninja buildsystem: cmake-ninja
name: mtxclient name: mtxclient
sources: sources:
- commit: eecc4e93f2137c658014f17cefd62ad569063769 - commit: ffc1d3e13a507fa501966b2d7e9d4eda881f6bf4
tag: v0.6.0
type: git type: git
url: https://github.com/Nheko-Reborn/mtxclient.git url: https://github.com/Nheko-Reborn/mtxclient.git
- config-opts: - config-opts:

View file

@ -0,0 +1 @@
<svg width="32" height="32" fill="none" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M4 13.999 13 14a2 2 0 0 1 1.995 1.85L15 16v1.5C14.999 21 11.284 22 8.5 22c-2.722 0-6.335-.956-6.495-4.27L2 17.5v-1.501c0-1.054.816-1.918 1.85-1.995L4 14ZM15.22 14H20c1.054 0 1.918.816 1.994 1.85L22 16v1c-.001 3.062-2.858 4-5 4a7.16 7.16 0 0 1-2.14-.322c.336-.386.607-.827.802-1.327A6.19 6.19 0 0 0 17 19.5l.267-.006c.985-.043 3.086-.363 3.226-2.289L20.5 17v-1a.501.501 0 0 0-.41-.492L20 15.5h-4.051a2.957 2.957 0 0 0-.595-1.34L15.22 14H20h-4.78ZM4 15.499l-.1.01a.51.51 0 0 0-.254.136.506.506 0 0 0-.136.253l-.01.101V17.5c0 1.009.45 1.722 1.417 2.242.826.445 2.003.714 3.266.753l.317.005.317-.005c1.263-.039 2.439-.308 3.266-.753.906-.488 1.359-1.145 1.412-2.057l.005-.186V16a.501.501 0 0 0-.41-.492L13 15.5l-9-.001ZM8.5 3a4.5 4.5 0 1 1 0 9 4.5 4.5 0 0 1 0-9Zm9 2a3.5 3.5 0 1 1 0 7 3.5 3.5 0 0 1 0-7Zm-9-.5c-1.654 0-3 1.346-3 3s1.346 3 3 3 3-1.346 3-3-1.346-3-3-3Zm9 2c-1.103 0-2 .897-2 2s.897 2 2 2 2-.897 2-2-.897-2-2-2Z" fill="#212121"/></svg>

After

Width:  |  Height:  |  Size: 1 KiB

View file

@ -17,6 +17,7 @@
<file>icons/ui/microphone-mute.svg</file> <file>icons/ui/microphone-mute.svg</file>
<file>icons/ui/microphone-unmute.svg</file> <file>icons/ui/microphone-unmute.svg</file>
<file>icons/ui/pause-symbol.svg</file> <file>icons/ui/pause-symbol.svg</file>
<file>icons/ui/people.svg</file>
<file>icons/ui/play-sign.svg</file> <file>icons/ui/play-sign.svg</file>
<file>icons/ui/power-off.svg</file> <file>icons/ui/power-off.svg</file>
<file>icons/ui/refresh.svg</file> <file>icons/ui/refresh.svg</file>

View file

@ -1640,7 +1640,7 @@ Cache::saveInvite(lmdb::txn &txn,
auto display_name = auto display_name =
msg->content.display_name.empty() ? msg->state_key : msg->content.display_name; msg->content.display_name.empty() ? msg->state_key : msg->content.display_name;
MemberInfo tmp{display_name, msg->content.avatar_url}; MemberInfo tmp{display_name, msg->content.avatar_url, msg->content.is_direct};
membersdb.put(txn, msg->state_key, json(tmp).dump()); membersdb.put(txn, msg->state_key, json(tmp).dump());
} else { } else {
@ -2777,7 +2777,8 @@ Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex,
try { try {
MemberInfo tmp = json::parse(user_data); MemberInfo tmp = json::parse(user_data);
members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)), members.emplace_back(RoomMember{QString::fromStdString(std::string(user_id)),
QString::fromStdString(tmp.name)}); QString::fromStdString(tmp.name),
tmp.is_direct});
} catch (const json::exception &e) { } catch (const json::exception &e) {
nhlog::db()->warn("{}", e.what()); nhlog::db()->warn("{}", e.what());
} }
@ -4563,6 +4564,8 @@ to_json(json &j, const MemberInfo &info)
{ {
j["name"] = info.name; j["name"] = info.name;
j["avatar_url"] = info.avatar_url; j["avatar_url"] = info.avatar_url;
if (info.is_direct)
j["is_direct"] = info.is_direct;
} }
void void
@ -4570,6 +4573,7 @@ from_json(const json &j, MemberInfo &info)
{ {
info.name = j.at("name"); info.name = j.at("name");
info.avatar_url = j.at("avatar_url"); info.avatar_url = j.at("avatar_url");
info.is_direct = j.value("is_direct", false);
} }
void void

View file

@ -26,6 +26,7 @@ struct RoomMember
{ {
QString user_id; QString user_id;
QString display_name; QString display_name;
bool is_direct = false;
}; };
//! Used to uniquely identify a list of read receipts. //! Used to uniquely identify a list of read receipts.
@ -98,6 +99,7 @@ struct MemberInfo
{ {
std::string name; std::string name;
std::string avatar_url; std::string avatar_url;
bool is_direct = false;
}; };
void void

View file

@ -176,7 +176,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
this, this,
&ChatPage::initializeViews, &ChatPage::initializeViews,
view_manager_, view_manager_,
[this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); }, [this](const mtx::responses::Sync &sync) { view_manager_->sync(sync); },
Qt::QueuedConnection); Qt::QueuedConnection);
connect(this, connect(this,
&ChatPage::initializeEmptyViews, &ChatPage::initializeEmptyViews,
@ -184,12 +184,12 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
&TimelineViewManager::initializeRoomlist); &TimelineViewManager::initializeRoomlist);
connect( connect(
this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged); this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) { connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) {
view_manager_->sync(rooms); view_manager_->sync(sync);
static unsigned int prevNotificationCount = 0; static unsigned int prevNotificationCount = 0;
unsigned int notificationCount = 0; unsigned int notificationCount = 0;
for (const auto &room : rooms.join) { for (const auto &room : sync.rooms.join) {
notificationCount += room.second.unread_notifications.notification_count; notificationCount += room.second.unread_notifications.notification_count;
} }
@ -583,7 +583,7 @@ ChatPage::startInitialSync()
olm::handle_to_device_messages(res.to_device.events); olm::handle_to_device_messages(res.to_device.events);
emit initializeViews(std::move(res.rooms)); emit initializeViews(std::move(res));
emit initializeMentions(cache::getTimelineMentions()); emit initializeMentions(cache::getTimelineMentions());
cache::calculateRoomReadStatus(); cache::calculateRoomReadStatus();
@ -622,7 +622,7 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string
auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res)); auto updates = cache::getRoomInfo(cache::client()->roomsWithStateUpdates(res));
emit syncUI(res.rooms); emit syncUI(std::move(res));
// if we process a lot of syncs (1 every 200ms), this means we clean the // if we process a lot of syncs (1 every 200ms), this means we clean the
// db every 100s // db every 100s
@ -662,8 +662,6 @@ ChatPage::trySync()
if (err) { if (err) {
const auto error = QString::fromStdString(err->matrix_error.error); const auto error = QString::fromStdString(err->matrix_error.error);
const auto msg = tr("Please try to login again: %1").arg(error); const auto msg = tr("Please try to login again: %1").arg(error);
const auto err_code = mtx::errors::to_string(err->matrix_error.errcode);
const int status_code = static_cast<int>(err->status_code);
if ((http::is_logged_in() && if ((http::is_logged_in() &&
(err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN || (err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN ||
@ -673,11 +671,7 @@ ChatPage::trySync()
return; return;
} }
nhlog::net()->error("sync error: {} {} {} {}", nhlog::net()->error("sync error: {}", *err);
err->parse_error,
status_code,
err->error_code,
err_code);
emit tryDelayedSyncCb(); emit tryDelayedSyncCb();
return; return;
} }

View file

@ -126,10 +126,10 @@ signals:
void newRoom(const QString &room_id); void newRoom(const QString &room_id);
void changeToRoom(const QString &room_id); void changeToRoom(const QString &room_id);
void initializeViews(const mtx::responses::Rooms &rooms); void initializeViews(const mtx::responses::Sync &rooms);
void initializeEmptyViews(); void initializeEmptyViews();
void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs); void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs);
void syncUI(const mtx::responses::Rooms &rooms); void syncUI(const mtx::responses::Sync &sync);
void dropToLoginPageCb(const QString &msg); void dropToLoginPageCb(const QString &msg);
void notifyMessage(const QString &roomid, void notifyMessage(const QString &roomid,

View file

@ -6,6 +6,10 @@
#include <mtxclient/http/client.hpp> #include <mtxclient/http/client.hpp>
#include <curl/curl.h>
#include "Logging.h"
namespace http { namespace http {
mtx::http::Client * mtx::http::Client *
client(); client();
@ -17,3 +21,115 @@ is_logged_in();
void void
init(); init();
} }
template<>
struct fmt::formatter<mtx::http::ClientError>
{
// Presentation format: 'f' - fixed, 'e' - exponential.
bool print_network_error = false;
bool print_http_error = false;
bool print_parser_error = false;
bool print_matrix_error = false;
// Parses format specifications of the form ['f' | 'e'].
constexpr auto parse(format_parse_context &ctx) -> decltype(ctx.begin())
{
// [ctx.begin(), ctx.end()) is a character range that contains a part of
// the format string starting from the format specifications to be parsed,
// e.g. in
//
// fmt::format("{:f} - point of interest", point{1, 2});
//
// the range will contain "f} - point of interest". The formatter should
// parse specifiers until '}' or the end of the range. In this example
// the formatter should parse the 'f' specifier and return an iterator
// pointing to '}'.
// Parse the presentation format and store it in the formatter:
auto it = ctx.begin(), end = ctx.end();
while (it != end && *it != '}') {
auto tmp = *it++;
switch (tmp) {
case 'n':
print_matrix_error = true;
break;
case 'h':
print_matrix_error = true;
break;
case 'p':
print_matrix_error = true;
break;
case 'm':
print_matrix_error = true;
break;
default:
throw format_error("invalid format specifier for mtx error");
}
}
// Check if reached the end of the range:
if (it != end && *it != '}')
throw format_error("invalid format");
// Return an iterator past the end of the parsed range:
return it;
}
// Formats the point p using the parsed format specification (presentation)
// stored in this formatter.
template<typename FormatContext>
auto format(const mtx::http::ClientError &e, FormatContext &ctx) -> decltype(ctx.out())
{
// ctx.out() is an output iterator to write to.
bool prepend_comma = false;
format_to(ctx.out(), "(");
if (print_network_error || e.error_code) {
format_to(ctx.out(), "connection: {}", e.error_code_string());
prepend_comma = true;
}
if (print_http_error ||
(e.status_code != 0 && (e.status_code < 200 || e.status_code >= 300))) {
if (prepend_comma)
format_to(ctx.out(), ", ");
format_to(ctx.out(), "http: {}", e.status_code);
prepend_comma = true;
}
if (print_parser_error || !e.parse_error.empty()) {
if (prepend_comma)
format_to(ctx.out(), ", ");
format_to(ctx.out(), "parser: {}", e.parse_error);
prepend_comma = true;
}
if (print_parser_error ||
(e.matrix_error.errcode != mtx::errors::ErrorCode::M_UNRECOGNIZED &&
!e.matrix_error.error.empty())) {
if (prepend_comma)
format_to(ctx.out(), ", ");
format_to(ctx.out(),
"matrix: {}:'{}'",
to_string(e.matrix_error.errcode),
e.matrix_error.error);
}
return format_to(ctx.out(), ")");
}
};
template<>
struct fmt::formatter<std::optional<mtx::http::ClientError>> : formatter<mtx::http::ClientError>
{
// parse is inherited from formatter<string_view>.
template<typename FormatContext>
auto format(std::optional<mtx::http::ClientError> c, FormatContext &ctx)
{
if (!c)
return format_to(ctx.out(), "(no error)");
else
return formatter<mtx::http::ClientError>::format(*c, ctx);
}
};

View file

@ -27,6 +27,7 @@
#include "Cache.h" #include "Cache.h"
#include "Config.h" #include "Config.h"
#include "EventAccessors.h" #include "EventAccessors.h"
#include "Logging.h"
#include "MatrixClient.h" #include "MatrixClient.h"
#include "UserSettingsPage.h" #include "UserSettingsPage.h"
@ -813,3 +814,65 @@ utils::isReply(const mtx::events::collections::TimelineEvents &e)
{ {
return mtx::accessors::relations(e).reply_to().has_value(); return mtx::accessors::relations(e).reply_to().has_value();
} }
void
utils::removeDirectFromRoom(QString roomid)
{
http::client()->get_account_data<mtx::events::account_data::Direct>(
[roomid](mtx::events::account_data::Direct ev, mtx::http::RequestErr e) {
if (e && e->status_code == 404)
ev = {};
else if (e) {
nhlog::net()->error("Failed to retrieve m.direct: {}", *e);
return;
}
auto r = roomid.toStdString();
for (auto it = ev.user_to_rooms.begin(); it != ev.user_to_rooms.end();) {
for (auto rit = it->second.begin(); rit != it->second.end();) {
if (r == *rit)
rit = it->second.erase(rit);
else
++rit;
}
if (it->second.empty())
it = ev.user_to_rooms.erase(it);
else
++it;
}
http::client()->put_account_data(ev, [r](mtx::http::RequestErr e) {
if (e)
nhlog::net()->error("Failed to update m.direct: {}", *e);
});
});
}
void
utils::markRoomAsDirect(QString roomid, std::vector<RoomMember> members)
{
http::client()->get_account_data<mtx::events::account_data::Direct>(
[roomid, members](mtx::events::account_data::Direct ev, mtx::http::RequestErr e) {
if (e && e->status_code == 404)
ev = {};
else if (e) {
nhlog::net()->error("Failed to retrieve m.direct: {}", *e);
return;
}
auto local = utils::localUser();
auto r = roomid.toStdString();
for (const auto &m : members) {
if (m.user_id != local) {
ev.user_to_rooms[m.user_id.toStdString()].push_back(r);
}
}
http::client()->put_account_data(ev, [r](mtx::http::RequestErr e) {
if (e)
nhlog::net()->error("Failed to update m.direct: {}", *e);
});
});
}

View file

@ -6,6 +6,7 @@
#include <variant> #include <variant>
#include <CacheStructs.h>
#include <QCoreApplication> #include <QCoreApplication>
#include <QDateTime> #include <QDateTime>
#include <QPixmap> #include <QPixmap>
@ -304,4 +305,10 @@ readImage(const QByteArray &data);
bool bool
isReply(const mtx::events::collections::TimelineEvents &e); isReply(const mtx::events::collections::TimelineEvents &e);
void
removeDirectFromRoom(QString roomid);
void
markRoomAsDirect(QString roomid, std::vector<RoomMember> members);
} }

View file

@ -44,8 +44,23 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
case CommunitiesModel::Roles::Id: case CommunitiesModel::Roles::Id:
return ""; return "";
} }
} else if (index.row() - 1 < spaceOrder_.size()) { } else if (index.row() == 1) {
auto id = spaceOrder_.at(index.row() - 1); switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
return QString(":/icons/icons/ui/people.svg");
case CommunitiesModel::Roles::DisplayName:
return tr("Direct Chats");
case CommunitiesModel::Roles::Tooltip:
return tr("Show direct chats.");
case CommunitiesModel::Roles::ChildrenHidden:
return false;
case CommunitiesModel::Roles::Hidden:
return hiddentTagIds_.contains("dm");
case CommunitiesModel::Roles::Id:
return "dm";
}
} else if (index.row() - 2 < spaceOrder_.size()) {
auto id = spaceOrder_.at(index.row() - 2);
switch (role) { switch (role) {
case CommunitiesModel::Roles::AvatarUrl: case CommunitiesModel::Roles::AvatarUrl:
return QString::fromStdString(spaces_.at(id).avatar_url); return QString::fromStdString(spaces_.at(id).avatar_url);
@ -59,8 +74,8 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
case CommunitiesModel::Roles::Id: case CommunitiesModel::Roles::Id:
return "space:" + id; return "space:" + id;
} }
} else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) { } else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) {
auto tag = tags_.at(index.row() - 1 - spaceOrder_.size()); auto tag = tags_.at(index.row() - 2 - spaceOrder_.size());
if (tag == "m.favourite") { if (tag == "m.favourite") {
switch (role) { switch (role) {
case CommunitiesModel::Roles::AvatarUrl: case CommunitiesModel::Roles::AvatarUrl:
@ -156,11 +171,11 @@ CommunitiesModel::clear()
} }
void void
CommunitiesModel::sync(const mtx::responses::Rooms &rooms) CommunitiesModel::sync(const mtx::responses::Sync &sync_)
{ {
bool tagsUpdated = false; bool tagsUpdated = false;
for (const auto &[roomid, room] : rooms.join) { for (const auto &[roomid, room] : sync_.rooms.join) {
(void)roomid; (void)roomid;
for (const auto &e : room.account_data.events) for (const auto &e : room.account_data.events)
if (std::holds_alternative< if (std::holds_alternative<
@ -182,11 +197,18 @@ CommunitiesModel::sync(const mtx::responses::Rooms &rooms)
tagsUpdated = true; tagsUpdated = true;
} }
} }
for (const auto &[roomid, room] : rooms.leave) { for (const auto &[roomid, room] : sync_.rooms.leave) {
(void)room; (void)room;
if (spaceOrder_.contains(QString::fromStdString(roomid))) if (spaceOrder_.contains(QString::fromStdString(roomid)))
tagsUpdated = true; tagsUpdated = true;
} }
for (const auto &e : sync_.account_data.events) {
if (std::holds_alternative<
mtx::events::AccountDataEvent<mtx::events::account_data::Direct>>(e)) {
tagsUpdated = true;
break;
}
}
if (tagsUpdated) if (tagsUpdated)
initializeSidebar(); initializeSidebar();
@ -213,6 +235,10 @@ CommunitiesModel::setCurrentTagId(QString tagId)
return; return;
} }
} }
} else if (tagId == "dm") {
this->currentTagId_ = tagId;
emit currentTagIdChanged(currentTagId_);
return;
} }
this->currentTagId_ = ""; this->currentTagId_ = "";
@ -239,6 +265,8 @@ CommunitiesModel::toggleTagId(QString tagId)
auto idx = spaceOrder_.indexOf(tagId.mid(6)); auto idx = spaceOrder_.indexOf(tagId.mid(6));
if (idx != -1) if (idx != -1)
emit dataChanged(index(idx + 1), index(idx + 1), {Hidden}); emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
} else if (tagId == "dm") {
emit dataChanged(index(1), index(1), {Hidden});
} }
emit hiddenTagsChanged(); emit hiddenTagsChanged();

View file

@ -37,13 +37,13 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override int rowCount(const QModelIndex &parent = QModelIndex()) const override
{ {
(void)parent; (void)parent;
return 1 + tags_.size() + spaceOrder_.size(); return 2 + tags_.size() + spaceOrder_.size();
} }
QVariant data(const QModelIndex &index, int role) const override; QVariant data(const QModelIndex &index, int role) const override;
public slots: public slots:
void initializeSidebar(); void initializeSidebar();
void sync(const mtx::responses::Rooms &rooms); void sync(const mtx::responses::Sync &sync_);
void clear(); void clear();
QString currentTagId() const { return currentTagId_; } QString currentTagId() const { return currentTagId_; }
void setCurrentTagId(QString tagId); void setCurrentTagId(QString tagId);

View file

@ -645,6 +645,11 @@ InputBar::command(QString command, QString args)
return; return;
} }
nhlog::net()->error("Could not resolve goto: {}", args.toStdString()); nhlog::net()->error("Could not resolve goto: {}", args.toStdString());
} else if (command == "converttodm") {
utils::markRoomAsDirect(this->room->roomId(),
cache::getMembers(this->room->roomId().toStdString(), 0, -1));
} else if (command == "converttoroom") {
utils::removeDirectFromRoom(this->room->roomId());
} }
} }

View file

@ -95,6 +95,10 @@ RoomlistModel::data(const QModelIndex &index, int role) const
return list; return list;
} else if (role == Roles::RoomId) { } else if (role == Roles::RoomId) {
return roomid; return roomid;
} else if (role == Roles::IsDirect) {
return directChatToUser.count(roomid) > 0;
} else if (role == Roles::DirectChatOtherUserId) {
return directChatToUser.count(roomid) ? directChatToUser.at(roomid).front() : "";
} }
if (models.contains(roomid)) { if (models.contains(roomid)) {
@ -129,10 +133,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
list.push_back(QString::fromStdString(t)); list.push_back(QString::fromStdString(t));
return list; return list;
} }
case Roles::IsDirect:
return room->isDirect();
case Roles::DirectChatOtherUserId:
return room->directChatOtherUserId();
default: default:
return {}; return {};
} }
@ -162,12 +162,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
return false; return false;
case Roles::Tags: case Roles::Tags:
return QStringList(); 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: default:
return {}; return {};
} }
@ -199,10 +193,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
return true; return true;
case Roles::Tags: case Roles::Tags:
return QStringList(); return QStringList();
case Roles::IsDirect:
return false;
case Roles::DirectChatOtherUserId:
return QString{}; // should never be reached
default: default:
return {}; return {};
} }
@ -443,10 +433,69 @@ RoomlistModel::fetchPreview(QString roomid_) const
}); });
} }
void std::set<QString>
RoomlistModel::sync(const mtx::responses::Rooms &rooms) RoomlistModel::updateDMs(mtx::events::AccountDataEvent<mtx::events::account_data::Direct> event)
{ {
for (const auto &[room_id, room] : rooms.join) { std::set<QString> roomsToUpdate;
std::map<QString, std::vector<QString>> directChatToUserTemp;
for (const auto &[user, rooms] : event.content.user_to_rooms) {
QString u = QString::fromStdString(user);
for (const auto &r : rooms) {
directChatToUserTemp[QString::fromStdString(r)].push_back(u);
}
}
for (auto l = directChatToUser.begin(), r = directChatToUserTemp.begin();
l != directChatToUser.end() && r != directChatToUserTemp.end();) {
if (l == directChatToUser.end()) {
while (r != directChatToUserTemp.end()) {
roomsToUpdate.insert(r->first);
++r;
}
} else if (r == directChatToUserTemp.end()) {
while (l != directChatToUser.end()) {
roomsToUpdate.insert(l->first);
++l;
}
} else if (l->first == r->first) {
if (l->second != r->second)
roomsToUpdate.insert(l->first);
++l;
++r;
} else if (l->first < r->first) {
roomsToUpdate.insert(l->first);
++l;
} else if (l->first > r->first) {
roomsToUpdate.insert(r->first);
++r;
} else {
throw std::logic_error("Infinite loop when updating DMs!");
}
}
this->directChatToUser = directChatToUserTemp;
return roomsToUpdate;
}
void
RoomlistModel::sync(const mtx::responses::Sync &sync_)
{
for (const auto &e : sync_.account_data.events) {
if (auto event =
std::get_if<mtx::events::AccountDataEvent<mtx::events::account_data::Direct>>(&e)) {
auto updatedDMs = updateDMs(*event);
for (const auto &r : updatedDMs) {
if (auto idx = roomidToIndex(r); idx != -1)
emit dataChanged(index(idx), index(idx), {IsDirect, DirectChatOtherUserId});
}
}
}
for (const auto &[room_id, room] : sync_.rooms.join) {
auto qroomid = QString::fromStdString(room_id); auto qroomid = QString::fromStdString(room_id);
// addRoom will only add the room, if it doesn't exist // addRoom will only add the room, if it doesn't exist
@ -477,7 +526,7 @@ RoomlistModel::sync(const mtx::responses::Rooms &rooms)
} }
} }
for (const auto &[room_id, room] : rooms.leave) { for (const auto &[room_id, room] : sync_.rooms.leave) {
(void)room; (void)room;
auto qroomid = QString::fromStdString(room_id); auto qroomid = QString::fromStdString(room_id);
@ -497,7 +546,7 @@ RoomlistModel::sync(const mtx::responses::Rooms &rooms)
} }
} }
for (const auto &[room_id, room] : rooms.invite) { for (const auto &[room_id, room] : sync_.rooms.invite) {
(void)room; (void)room;
auto qroomid = QString::fromStdString(room_id); auto qroomid = QString::fromStdString(room_id);
@ -527,9 +576,19 @@ RoomlistModel::initializeRooms()
invites.clear(); invites.clear();
currentRoom_ = nullptr; currentRoom_ = nullptr;
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())) {
updateDMs(*event);
}
}
invites = cache::client()->invites(); invites = cache::client()->invites();
for (const auto &id : invites.keys()) for (const auto &id : invites.keys()) {
roomids.push_back(id); roomids.push_back(id);
}
for (const auto &id : cache::client()->roomIds()) for (const auto &id : cache::client()->roomIds())
addRoom(id, true); addRoom(id, true);
@ -568,6 +627,16 @@ RoomlistModel::acceptInvite(QString roomid)
{ {
if (invites.contains(roomid)) { if (invites.contains(roomid)) {
// Don't remove invite yet, so that we can switch to it // Don't remove invite yet, so that we can switch to it
auto members = cache::getMembersFromInvite(roomid.toStdString(), 0, -1);
auto local = utils::localUser();
for (const auto &m : members) {
if (m.user_id == local && m.is_direct) {
nhlog::db()->info("marking {} as direct", roomid.toStdString());
utils::markRoomAsDirect(roomid, members);
break;
}
}
ChatPage::instance()->joinRoom(roomid); ChatPage::instance()->joinRoom(roomid);
} }
} }
@ -756,11 +825,14 @@ FilteredRoomlistModel::updateHiddenTagsAndSpaces()
{ {
hiddenTags.clear(); hiddenTags.clear();
hiddenSpaces.clear(); hiddenSpaces.clear();
hideDMs = false;
for (const auto &t : UserSettings::instance()->hiddenTags()) { for (const auto &t : UserSettings::instance()->hiddenTags()) {
if (t.startsWith("tag:")) if (t.startsWith("tag:"))
hiddenTags.push_back(t.mid(4)); hiddenTags.push_back(t.mid(4));
else if (t.startsWith("space:")) else if (t.startsWith("space:"))
hiddenSpaces.push_back(t.mid(6)); hiddenSpaces.push_back(t.mid(6));
else if (t == "dm")
hideDMs = true;
} }
invalidateFilter(); invalidateFilter();
@ -801,7 +873,48 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons
return false; return false;
} }
if (hideDMs) {
return !sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect)
.toBool();
}
return true; return true;
} else if (filterType == FilterBy::DirectChats) {
if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
.toBool()) {
return false;
}
if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsSpace)
.toBool()) {
return false;
}
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()) {
auto parents = sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::ParentSpaces)
.toStringList();
for (const auto &t : parents)
if (hiddenSpaces.contains(t))
return false;
}
return sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect)
.toBool();
} else if (filterType == FilterBy::Tag) { } else if (filterType == FilterBy::Tag) {
if (sourceModel() if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview) ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
@ -837,6 +950,12 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons
return false; return false;
} }
if (hideDMs) {
return !sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect)
.toBool();
}
return true; return true;
} else if (filterType == FilterBy::Space) { } else if (filterType == FilterBy::Space) {
if (filterStr == sourceModel() if (filterStr == sourceModel()
@ -874,6 +993,12 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons
return false; return false;
} }
if (hideDMs) {
return !sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect)
.toBool();
}
return true; return true;
} else { } else {
return true; return true;

View file

@ -87,7 +87,7 @@ public:
public slots: public slots:
void initializeRooms(); void initializeRooms();
void sync(const mtx::responses::Rooms &rooms); void sync(const mtx::responses::Sync &sync_);
void clear(); void clear();
int roomidToIndex(QString roomid) int roomidToIndex(QString roomid)
{ {
@ -123,6 +123,7 @@ signals:
private: private:
void addRoom(const QString &room_id, bool suppressInsertNotification = false); void addRoom(const QString &room_id, bool suppressInsertNotification = false);
void fetchPreview(QString roomid) const; void fetchPreview(QString roomid) const;
std::set<QString> updateDMs(mtx::events::AccountDataEvent<mtx::events::account_data::Direct> e);
TimelineViewManager *manager = nullptr; TimelineViewManager *manager = nullptr;
std::vector<QString> roomids; std::vector<QString> roomids;
@ -134,6 +135,8 @@ private:
QSharedPointer<TimelineModel> currentRoom_; QSharedPointer<TimelineModel> currentRoom_;
std::optional<RoomPreview> currentRoomPreview_; std::optional<RoomPreview> currentRoomPreview_;
std::map<QString, std::vector<QString>> directChatToUser;
friend class FilteredRoomlistModel; friend class FilteredRoomlistModel;
}; };
@ -180,6 +183,9 @@ public slots:
} else if (tagId.startsWith("space:")) { } else if (tagId.startsWith("space:")) {
filterType = FilterBy::Space; filterType = FilterBy::Space;
filterStr = tagId.mid(6); filterStr = tagId.mid(6);
} else if (tagId.startsWith("dm")) {
filterType = FilterBy::DirectChats;
filterStr.clear();
} else { } else {
filterType = FilterBy::Nothing; filterType = FilterBy::Nothing;
filterStr.clear(); filterStr.clear();
@ -202,9 +208,11 @@ private:
{ {
Tag, Tag,
Space, Space,
DirectChats,
Nothing, Nothing,
}; };
QString filterStr = ""; QString filterStr = "";
FilterBy filterType = FilterBy::Nothing; FilterBy filterType = FilterBy::Nothing;
QStringList hiddenTags, hiddenSpaces; QStringList hiddenTags, hiddenSpaces;
bool hideDMs = false;
}; };

View file

@ -359,10 +359,10 @@ TimelineViewManager::setVideoCallItem()
} }
void void
TimelineViewManager::sync(const mtx::responses::Rooms &rooms_res) TimelineViewManager::sync(const mtx::responses::Sync &sync_)
{ {
this->rooms_->sync(rooms_res); this->rooms_->sync(sync_);
this->communities_->sync(rooms_res); this->communities_->sync(sync_);
if (isInitialSync_) { if (isInitialSync_) {
this->isInitialSync_ = false; this->isInitialSync_ = false;

View file

@ -48,7 +48,7 @@ public:
TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr); TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
QWidget *getWidget() const { return container; } QWidget *getWidget() const { return container; }
void sync(const mtx::responses::Rooms &rooms); void sync(const mtx::responses::Sync &sync_);
MxcImageProvider *imageProvider() { return imgProvider; } MxcImageProvider *imageProvider() { return imgProvider; }
CallManager *callManager() { return callManager_; } CallManager *callManager() { return callManager_; }