summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--resources/icons/ui/people.svg1
-rw-r--r--resources/res.qrc1
-rw-r--r--src/ChatPage.cpp12
-rw-r--r--src/ChatPage.h4
-rw-r--r--src/timeline/CommunitiesModel.cpp42
-rw-r--r--src/timeline/CommunitiesModel.h4
-rw-r--r--src/timeline/RoomlistModel.cpp150
-rw-r--r--src/timeline/RoomlistModel.h10
-rw-r--r--src/timeline/TimelineViewManager.cpp6
-rw-r--r--src/timeline/TimelineViewManager.h2
10 files changed, 192 insertions, 40 deletions
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 @@
+<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>
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 @@
         <file>icons/ui/microphone-mute.svg</file>
         <file>icons/ui/microphone-unmute.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/power-off.svg</file>
         <file>icons/ui/refresh.svg</file>
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 77a8edcf..4b37864b 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -176,7 +176,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> 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> 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
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<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 notifyMessage(const QString &roomid,
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<mtx::events::account_data::Direct>>(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/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index cc9ff800..53fd9498 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
       });
 }
 
+std::set<QString>
+RoomlistModel::updateDMs(mtx::events::AccountDataEvent<mtx::events::account_data::Direct> event)
+{
+    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::Rooms &rooms)
+RoomlistModel::sync(const mtx::responses::Sync &sync_)
 {
-    for (const auto &[room_id, room] : rooms.join) {
+    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);
 
         // 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,6 +576,15 @@ RoomlistModel::initializeRooms()
     invites.clear();
     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();
     for (const auto &id : invites.keys())
         roomids.push_back(id);
@@ -756,11 +814,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 +862,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 +939,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 +982,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<QString> updateDMs(mtx::events::AccountDataEvent<mtx::events::account_data::Direct> e);
 
     TimelineViewManager *manager = nullptr;
     std::vector<QString> roomids;
@@ -134,6 +135,8 @@ private:
     QSharedPointer<TimelineModel> currentRoom_;
     std::optional<RoomPreview> currentRoomPreview_;
 
+    std::map<QString, std::vector<QString>> 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_; }