diff --git a/CMakeLists.txt b/CMakeLists.txt
index 6d84df5f..4276d2f1 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -413,7 +413,7 @@ if(USE_BUNDLED_MTXCLIENT)
FetchContent_Declare(
MatrixClient
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_TESTS OFF CACHE INTERNAL "")
@@ -644,7 +644,7 @@ endif()
if(WIN32)
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()
add_executable (nheko ${OS_BUNDLE} ${NHEKO_DEPS})
@@ -710,7 +710,7 @@ if(USE_BUNDLED_COEURL)
FetchContent_Declare(
coeurl
GIT_REPOSITORY https://nheko.im/Nheko-Reborn/coeurl.git
- GIT_TAG v0.1.0
+ GIT_TAG abafd60d7e9f5cce76c9abad3b2b3dc1382e5349
)
FetchContent_MakeAvailable(coeurl)
target_link_libraries(nheko PUBLIC coeurl::coeurl)
diff --git a/io.github.NhekoReborn.Nheko.yaml b/io.github.NhekoReborn.Nheko.yaml
index 70c3af83..22e52ae8 100644
--- a/io.github.NhekoReborn.Nheko.yaml
+++ b/io.github.NhekoReborn.Nheko.yaml
@@ -175,8 +175,7 @@ modules:
- -Ddefault_library=static
name: coeurl
sources:
- - commit: a08f619adaa1ccd34eb6315d6578eddae0d1cc9b
- tag: v0.1.0
+ - commit: abafd60d7e9f5cce76c9abad3b2b3dc1382e5349
type: git
url: https://nheko.im/nheko-reborn/coeurl.git
- config-opts:
@@ -187,8 +186,7 @@ modules:
buildsystem: cmake-ninja
name: mtxclient
sources:
- - commit: eecc4e93f2137c658014f17cefd62ad569063769
- tag: v0.6.0
+ - commit: ffc1d3e13a507fa501966b2d7e9d4eda881f6bf4
type: git
url: https://github.com/Nheko-Reborn/mtxclient.git
- config-opts:
diff --git a/resources/icons/ui/people.svg b/resources/icons/ui/people.svg
new file mode 100644
index 00000000..7c284050
--- /dev/null
+++ b/resources/icons/ui/people.svg
@@ -0,0 +1 @@
+
diff --git a/resources/res.qrc b/resources/res.qrc
index 838aeadb..83edc941 100644
--- a/resources/res.qrc
+++ b/resources/res.qrc
@@ -17,6 +17,7 @@
icons/ui/microphone-mute.svg
icons/ui/microphone-unmute.svg
icons/ui/pause-symbol.svg
+ icons/ui/people.svg
icons/ui/play-sign.svg
icons/ui/power-off.svg
icons/ui/refresh.svg
diff --git a/src/Cache.cpp b/src/Cache.cpp
index c22cd0d6..4f96f430 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -1640,7 +1640,7 @@ Cache::saveInvite(lmdb::txn &txn,
auto 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());
} else {
@@ -2777,7 +2777,8 @@ Cache::getMembersFromInvite(const std::string &room_id, std::size_t startIndex,
try {
MemberInfo tmp = json::parse(user_data);
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) {
nhlog::db()->warn("{}", e.what());
}
@@ -4563,6 +4564,8 @@ to_json(json &j, const MemberInfo &info)
{
j["name"] = info.name;
j["avatar_url"] = info.avatar_url;
+ if (info.is_direct)
+ j["is_direct"] = info.is_direct;
}
void
@@ -4570,6 +4573,7 @@ from_json(const json &j, MemberInfo &info)
{
info.name = j.at("name");
info.avatar_url = j.at("avatar_url");
+ info.is_direct = j.value("is_direct", false);
}
void
diff --git a/src/CacheStructs.h b/src/CacheStructs.h
index e28f5b2d..01a050da 100644
--- a/src/CacheStructs.h
+++ b/src/CacheStructs.h
@@ -26,6 +26,7 @@ struct RoomMember
{
QString user_id;
QString display_name;
+ bool is_direct = false;
};
//! Used to uniquely identify a list of read receipts.
@@ -98,6 +99,7 @@ struct MemberInfo
{
std::string name;
std::string avatar_url;
+ bool is_direct = false;
};
void
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 77a8edcf..c1c7eb7d 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -176,7 +176,7 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
this,
&ChatPage::initializeViews,
view_manager_,
- [this](const mtx::responses::Rooms &rooms) { view_manager_->sync(rooms); },
+ [this](const mtx::responses::Sync &sync) { view_manager_->sync(sync); },
Qt::QueuedConnection);
connect(this,
&ChatPage::initializeEmptyViews,
@@ -184,12 +184,12 @@ ChatPage::ChatPage(QSharedPointer userSettings, QWidget *parent)
&TimelineViewManager::initializeRoomlist);
connect(
this, &ChatPage::chatFocusChanged, view_manager_, &TimelineViewManager::chatFocusChanged);
- connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Rooms &rooms) {
- view_manager_->sync(rooms);
+ connect(this, &ChatPage::syncUI, this, [this](const mtx::responses::Sync &sync) {
+ view_manager_->sync(sync);
static unsigned int prevNotificationCount = 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;
}
@@ -583,7 +583,7 @@ ChatPage::startInitialSync()
olm::handle_to_device_messages(res.to_device.events);
- emit initializeViews(std::move(res.rooms));
+ emit initializeViews(std::move(res));
emit initializeMentions(cache::getTimelineMentions());
cache::calculateRoomReadStatus();
@@ -622,7 +622,7 @@ ChatPage::handleSyncResponse(const mtx::responses::Sync &res, const std::string
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
// db every 100s
@@ -660,10 +660,8 @@ ChatPage::trySync()
http::client()->sync(
opts, [this, since = opts.since](const mtx::responses::Sync &res, mtx::http::RequestErr err) {
if (err) {
- const auto error = QString::fromStdString(err->matrix_error.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(err->status_code);
+ const auto error = QString::fromStdString(err->matrix_error.error);
+ const auto msg = tr("Please try to login again: %1").arg(error);
if ((http::is_logged_in() &&
(err->matrix_error.errcode == mtx::errors::ErrorCode::M_UNKNOWN_TOKEN ||
@@ -673,11 +671,7 @@ ChatPage::trySync()
return;
}
- nhlog::net()->error("sync error: {} {} {} {}",
- err->parse_error,
- status_code,
- err->error_code,
- err_code);
+ nhlog::net()->error("sync error: {}", *err);
emit tryDelayedSyncCb();
return;
}
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 8f3dc53e..c572f94b 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -126,10 +126,10 @@ signals:
void newRoom(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 initializeMentions(const QMap ¬ifs);
- void syncUI(const mtx::responses::Rooms &rooms);
+ void syncUI(const mtx::responses::Sync &sync);
void dropToLoginPageCb(const QString &msg);
void notifyMessage(const QString &roomid,
diff --git a/src/MatrixClient.h b/src/MatrixClient.h
index 605ba5e0..7d86537b 100644
--- a/src/MatrixClient.h
+++ b/src/MatrixClient.h
@@ -6,6 +6,10 @@
#include
+#include
+
+#include "Logging.h"
+
namespace http {
mtx::http::Client *
client();
@@ -17,3 +21,115 @@ is_logged_in();
void
init();
}
+
+template<>
+struct fmt::formatter
+{
+ // 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
+ 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> : formatter
+{
+ // parse is inherited from formatter.
+ template
+ auto format(std::optional c, FormatContext &ctx)
+ {
+ if (!c)
+ return format_to(ctx.out(), "(no error)");
+ else
+ return formatter::format(*c, ctx);
+ }
+};
diff --git a/src/Utils.cpp b/src/Utils.cpp
index 59b02298..dda6f685 100644
--- a/src/Utils.cpp
+++ b/src/Utils.cpp
@@ -27,6 +27,7 @@
#include "Cache.h"
#include "Config.h"
#include "EventAccessors.h"
+#include "Logging.h"
#include "MatrixClient.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();
}
+
+void
+utils::removeDirectFromRoom(QString roomid)
+{
+ http::client()->get_account_data(
+ [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 members)
+{
+ http::client()->get_account_data(
+ [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);
+ });
+ });
+}
diff --git a/src/Utils.h b/src/Utils.h
index da82ec7c..701ec8fc 100644
--- a/src/Utils.h
+++ b/src/Utils.h
@@ -6,6 +6,7 @@
#include
+#include
#include
#include
#include
@@ -304,4 +305,10 @@ readImage(const QByteArray &data);
bool
isReply(const mtx::events::collections::TimelineEvents &e);
+
+void
+removeDirectFromRoom(QString roomid);
+
+void
+markRoomAsDirect(QString roomid, std::vector members);
}
diff --git a/src/timeline/CommunitiesModel.cpp b/src/timeline/CommunitiesModel.cpp
index 47ff521d..90f1532b 100644
--- a/src/timeline/CommunitiesModel.cpp
+++ b/src/timeline/CommunitiesModel.cpp
@@ -44,8 +44,23 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
case CommunitiesModel::Roles::Id:
return "";
}
- } else if (index.row() - 1 < spaceOrder_.size()) {
- auto id = spaceOrder_.at(index.row() - 1);
+ } else if (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) {
case CommunitiesModel::Roles::AvatarUrl:
return QString::fromStdString(spaces_.at(id).avatar_url);
@@ -59,8 +74,8 @@ CommunitiesModel::data(const QModelIndex &index, int role) const
case CommunitiesModel::Roles::Id:
return "space:" + id;
}
- } else if (index.row() - 1 < tags_.size() + spaceOrder_.size()) {
- auto tag = tags_.at(index.row() - 1 - spaceOrder_.size());
+ } else if (index.row() - 2 < tags_.size() + spaceOrder_.size()) {
+ auto tag = tags_.at(index.row() - 2 - spaceOrder_.size());
if (tag == "m.favourite") {
switch (role) {
case CommunitiesModel::Roles::AvatarUrl:
@@ -156,11 +171,11 @@ CommunitiesModel::clear()
}
void
-CommunitiesModel::sync(const mtx::responses::Rooms &rooms)
+CommunitiesModel::sync(const mtx::responses::Sync &sync_)
{
bool tagsUpdated = false;
- for (const auto &[roomid, room] : rooms.join) {
+ for (const auto &[roomid, room] : sync_.rooms.join) {
(void)roomid;
for (const auto &e : room.account_data.events)
if (std::holds_alternative<
@@ -182,11 +197,18 @@ CommunitiesModel::sync(const mtx::responses::Rooms &rooms)
tagsUpdated = true;
}
}
- for (const auto &[roomid, room] : rooms.leave) {
+ for (const auto &[roomid, room] : sync_.rooms.leave) {
(void)room;
if (spaceOrder_.contains(QString::fromStdString(roomid)))
tagsUpdated = true;
}
+ for (const auto &e : sync_.account_data.events) {
+ if (std::holds_alternative<
+ mtx::events::AccountDataEvent>(e)) {
+ tagsUpdated = true;
+ break;
+ }
+ }
if (tagsUpdated)
initializeSidebar();
@@ -213,6 +235,10 @@ CommunitiesModel::setCurrentTagId(QString tagId)
return;
}
}
+ } else if (tagId == "dm") {
+ this->currentTagId_ = tagId;
+ emit currentTagIdChanged(currentTagId_);
+ return;
}
this->currentTagId_ = "";
@@ -239,6 +265,8 @@ CommunitiesModel::toggleTagId(QString tagId)
auto idx = spaceOrder_.indexOf(tagId.mid(6));
if (idx != -1)
emit dataChanged(index(idx + 1), index(idx + 1), {Hidden});
+ } else if (tagId == "dm") {
+ emit dataChanged(index(1), index(1), {Hidden});
}
emit hiddenTagsChanged();
diff --git a/src/timeline/CommunitiesModel.h b/src/timeline/CommunitiesModel.h
index 0440d17f..114e3f94 100644
--- a/src/timeline/CommunitiesModel.h
+++ b/src/timeline/CommunitiesModel.h
@@ -37,13 +37,13 @@ public:
int rowCount(const QModelIndex &parent = QModelIndex()) const override
{
(void)parent;
- return 1 + tags_.size() + spaceOrder_.size();
+ return 2 + tags_.size() + spaceOrder_.size();
}
QVariant data(const QModelIndex &index, int role) const override;
public slots:
void initializeSidebar();
- void sync(const mtx::responses::Rooms &rooms);
+ void sync(const mtx::responses::Sync &sync_);
void clear();
QString currentTagId() const { return currentTagId_; }
void setCurrentTagId(QString tagId);
diff --git a/src/timeline/InputBar.cpp b/src/timeline/InputBar.cpp
index 44df3411..bd4f59d8 100644
--- a/src/timeline/InputBar.cpp
+++ b/src/timeline/InputBar.cpp
@@ -645,6 +645,11 @@ InputBar::command(QString command, QString args)
return;
}
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());
}
}
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index cc9ff800..79324628 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -95,6 +95,10 @@ RoomlistModel::data(const QModelIndex &index, int role) const
return list;
} else if (role == Roles::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)) {
@@ -129,10 +133,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
list.push_back(QString::fromStdString(t));
return list;
}
- case Roles::IsDirect:
- return room->isDirect();
- case Roles::DirectChatOtherUserId:
- return room->directChatOtherUserId();
default:
return {};
}
@@ -162,12 +162,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
return false;
case Roles::Tags:
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:
return {};
}
@@ -199,10 +193,6 @@ RoomlistModel::data(const QModelIndex &index, int role) const
return true;
case Roles::Tags:
return QStringList();
- case Roles::IsDirect:
- return false;
- case Roles::DirectChatOtherUserId:
- return QString{}; // should never be reached
default:
return {};
}
@@ -443,10 +433,69 @@ RoomlistModel::fetchPreview(QString roomid_) const
});
}
-void
-RoomlistModel::sync(const mtx::responses::Rooms &rooms)
+std::set
+RoomlistModel::updateDMs(mtx::events::AccountDataEvent event)
{
- for (const auto &[room_id, room] : rooms.join) {
+ std::set roomsToUpdate;
+ std::map> 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>(&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);
// 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;
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;
auto qroomid = QString::fromStdString(room_id);
@@ -527,9 +576,19 @@ RoomlistModel::initializeRooms()
invites.clear();
currentRoom_ = nullptr;
+ auto e = cache::client()->getAccountData(mtx::events::EventType::Direct);
+ if (e) {
+ if (auto event =
+ std::get_if>(
+ &e.value())) {
+ updateDMs(*event);
+ }
+ }
+
invites = cache::client()->invites();
- for (const auto &id : invites.keys())
+ for (const auto &id : invites.keys()) {
roomids.push_back(id);
+ }
for (const auto &id : cache::client()->roomIds())
addRoom(id, true);
@@ -568,6 +627,16 @@ RoomlistModel::acceptInvite(QString roomid)
{
if (invites.contains(roomid)) {
// 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);
}
}
@@ -756,11 +825,14 @@ FilteredRoomlistModel::updateHiddenTagsAndSpaces()
{
hiddenTags.clear();
hiddenSpaces.clear();
+ hideDMs = false;
for (const auto &t : UserSettings::instance()->hiddenTags()) {
if (t.startsWith("tag:"))
hiddenTags.push_back(t.mid(4));
else if (t.startsWith("space:"))
hiddenSpaces.push_back(t.mid(6));
+ else if (t == "dm")
+ hideDMs = true;
}
invalidateFilter();
@@ -801,7 +873,48 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons
return false;
}
+ if (hideDMs) {
+ return !sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect)
+ .toBool();
+ }
+
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) {
if (sourceModel()
->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsPreview)
@@ -837,6 +950,12 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons
return false;
}
+ if (hideDMs) {
+ return !sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect)
+ .toBool();
+ }
+
return true;
} else if (filterType == FilterBy::Space) {
if (filterStr == sourceModel()
@@ -874,6 +993,12 @@ FilteredRoomlistModel::filterAcceptsRow(int sourceRow, const QModelIndex &) cons
return false;
}
+ if (hideDMs) {
+ return !sourceModel()
+ ->data(sourceModel()->index(sourceRow, 0), RoomlistModel::IsDirect)
+ .toBool();
+ }
+
return true;
} else {
return true;
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index 458e0fe7..5d0bcd53 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -87,7 +87,7 @@ public:
public slots:
void initializeRooms();
- void sync(const mtx::responses::Rooms &rooms);
+ void sync(const mtx::responses::Sync &sync_);
void clear();
int roomidToIndex(QString roomid)
{
@@ -123,6 +123,7 @@ signals:
private:
void addRoom(const QString &room_id, bool suppressInsertNotification = false);
void fetchPreview(QString roomid) const;
+ std::set updateDMs(mtx::events::AccountDataEvent e);
TimelineViewManager *manager = nullptr;
std::vector roomids;
@@ -134,6 +135,8 @@ private:
QSharedPointer currentRoom_;
std::optional currentRoomPreview_;
+ std::map> directChatToUser;
+
friend class FilteredRoomlistModel;
};
@@ -180,6 +183,9 @@ public slots:
} else if (tagId.startsWith("space:")) {
filterType = FilterBy::Space;
filterStr = tagId.mid(6);
+ } else if (tagId.startsWith("dm")) {
+ filterType = FilterBy::DirectChats;
+ filterStr.clear();
} else {
filterType = FilterBy::Nothing;
filterStr.clear();
@@ -202,9 +208,11 @@ private:
{
Tag,
Space,
+ DirectChats,
Nothing,
};
QString filterStr = "";
FilterBy filterType = FilterBy::Nothing;
QStringList hiddenTags, hiddenSpaces;
+ bool hideDMs = false;
};
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index c5fe6b4b..07fb0417 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -359,10 +359,10 @@ TimelineViewManager::setVideoCallItem()
}
void
-TimelineViewManager::sync(const mtx::responses::Rooms &rooms_res)
+TimelineViewManager::sync(const mtx::responses::Sync &sync_)
{
- this->rooms_->sync(rooms_res);
- this->communities_->sync(rooms_res);
+ this->rooms_->sync(sync_);
+ this->communities_->sync(sync_);
if (isInitialSync_) {
this->isInitialSync_ = false;
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 3e3952a8..a4b49829 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -48,7 +48,7 @@ public:
TimelineViewManager(CallManager *callManager, ChatPage *parent = nullptr);
QWidget *getWidget() const { return container; }
- void sync(const mtx::responses::Rooms &rooms);
+ void sync(const mtx::responses::Sync &sync_);
MxcImageProvider *imageProvider() { return imgProvider; }
CallManager *callManager() { return callManager_; }