summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--resources/qml/RoomList.qml56
-rw-r--r--src/Cache.cpp50
-rw-r--r--src/Cache.h2
-rw-r--r--src/Cache_p.h3
-rw-r--r--src/ChatPage.cpp4
-rw-r--r--src/ChatPage.h2
-rw-r--r--src/RoomList.cpp2
-rw-r--r--src/RoomList.h2
-rw-r--r--src/timeline/RoomlistModel.cpp169
-rw-r--r--src/timeline/RoomlistModel.h8
-rw-r--r--src/timeline/TimelineViewManager.cpp4
-rw-r--r--src/timeline/TimelineViewManager.h2
-rw-r--r--src/ui/Theme.cpp3
-rw-r--r--src/ui/Theme.h4
14 files changed, 260 insertions, 51 deletions
diff --git a/resources/qml/RoomList.qml b/resources/qml/RoomList.qml
index 40669eda..e9bb351f 100644
--- a/resources/qml/RoomList.qml
+++ b/resources/qml/RoomList.qml
@@ -141,6 +141,8 @@ Page {
                     RowLayout {
                         Layout.fillWidth: true
                         spacing: 0
+                        visible: !model.isInvite
+                        height: visible ? 0 : undefined
 
                         ElidedLabel {
                             color: roomItem.unimportantText
@@ -182,6 +184,60 @@ Page {
 
                     }
 
+                    RowLayout {
+                        Layout.fillWidth: true
+                        spacing: Nheko.paddingMedium
+                        visible: model.isInvite
+                        enabled: visible
+                        height: visible ? 0 : undefined
+
+                        ElidedLabel {
+                            elideWidth: textContent.width / 2 - 2 * Nheko.paddingMedium
+                            fullText: qsTr("Accept")
+                            horizontalAlignment: Text.AlignHCenter
+                            verticalAlignment: Text.AlignVCenter
+                            leftPadding: Nheko.paddingMedium
+                            rightPadding: Nheko.paddingMedium
+                            color: Nheko.colors.brightText
+
+                            TapHandler {
+                                onSingleTapped: Rooms.acceptInvite(model.roomId)
+                            }
+
+                            background: Rectangle {
+                                color: Nheko.theme.alternateButton
+                                radius: height / 2
+                            }
+
+                        }
+
+                        ElidedLabel {
+                            Layout.alignment: Qt.AlignRight
+                            elideWidth: textContent.width / 2 - 2 * Nheko.paddingMedium
+                            fullText: qsTr("Decline")
+                            horizontalAlignment: Text.AlignHCenter
+                            verticalAlignment: Text.AlignVCenter
+                            leftPadding: Nheko.paddingMedium
+                            rightPadding: Nheko.paddingMedium
+                            color: Nheko.colors.brightText
+
+                            TapHandler {
+                                onSingleTapped: Rooms.declineInvite(model.roomId)
+                            }
+
+                            background: Rectangle {
+                                color: Nheko.theme.alternateButton
+                                radius: height / 2
+                            }
+
+                        }
+
+                        Item {
+                            Layout.fillWidth: true
+                        }
+
+                    }
+
                 }
 
             }
diff --git a/src/Cache.cpp b/src/Cache.cpp
index c41b66cc..4a99dd59 100644
--- a/src/Cache.cpp
+++ b/src/Cache.cpp
@@ -2045,21 +2045,57 @@ Cache::getLastMessageInfo(lmdb::txn &txn, const std::string &room_id)
         return fallbackDesc;
 }
 
-std::map<QString, bool>
+QHash<QString, RoomInfo>
 Cache::invites()
 {
-        std::map<QString, bool> result;
+        QHash<QString, RoomInfo> result;
 
         auto txn    = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
         auto cursor = lmdb::cursor::open(txn, invitesDb_);
 
-        std::string_view room_id, unused;
+        std::string_view room_id, room_data;
 
-        while (cursor.get(room_id, unused, MDB_NEXT))
-                result.emplace(QString::fromStdString(std::string(room_id)), true);
+        while (cursor.get(room_id, room_data, MDB_NEXT)) {
+                try {
+                        RoomInfo tmp     = json::parse(room_data);
+                        tmp.member_count = getInviteMembersDb(txn, std::string(room_id)).size(txn);
+                        result.insert(QString::fromStdString(std::string(room_id)), std::move(tmp));
+                } catch (const json::exception &e) {
+                        nhlog::db()->warn("failed to parse room info for invite: "
+                                          "room_id ({}), {}: {}",
+                                          room_id,
+                                          std::string(room_data),
+                                          e.what());
+                }
+        }
 
         cursor.close();
-        txn.commit();
+
+        return result;
+}
+
+std::optional<RoomInfo>
+Cache::invite(std::string_view roomid)
+{
+        std::optional<RoomInfo> result;
+
+        auto txn = lmdb::txn::begin(env_, nullptr, MDB_RDONLY);
+
+        std::string_view room_data;
+
+        if (invitesDb_.get(txn, roomid, room_data)) {
+                try {
+                        RoomInfo tmp     = json::parse(room_data);
+                        tmp.member_count = getInviteMembersDb(txn, std::string(roomid)).size(txn);
+                        result           = std::move(tmp);
+                } catch (const json::exception &e) {
+                        nhlog::db()->warn("failed to parse room info for invite: "
+                                          "room_id ({}), {}: {}",
+                                          roomid,
+                                          std::string(room_data),
+                                          e.what());
+                }
+        }
 
         return result;
 }
@@ -4064,7 +4100,7 @@ roomInfo(bool withInvites)
 {
         return instance_->roomInfo(withInvites);
 }
-std::map<QString, bool>
+QHash<QString, RoomInfo>
 invites()
 {
         return instance_->invites();
diff --git a/src/Cache.h b/src/Cache.h
index 427dbafc..74ec9695 100644
--- a/src/Cache.h
+++ b/src/Cache.h
@@ -62,7 +62,7 @@ joinedRooms();
 
 QMap<QString, RoomInfo>
 roomInfo(bool withInvites = true);
-std::map<QString, bool>
+QHash<QString, RoomInfo>
 invites();
 
 //! Calculate & return the name of the room.
diff --git a/src/Cache_p.h b/src/Cache_p.h
index c55fa601..f2911622 100644
--- a/src/Cache_p.h
+++ b/src/Cache_p.h
@@ -70,7 +70,8 @@ public:
 
         QMap<QString, RoomInfo> roomInfo(bool withInvites = true);
         std::optional<mtx::events::state::CanonicalAlias> getRoomAliases(const std::string &roomid);
-        std::map<QString, bool> invites();
+        QHash<QString, RoomInfo> invites();
+        std::optional<RoomInfo> invite(std::string_view roomid);
 
         //! Calculate & return the name of the room.
         QString getRoomName(lmdb::txn &txn, lmdb::dbi &statesdb, lmdb::dbi &membersdb);
diff --git a/src/ChatPage.cpp b/src/ChatPage.cpp
index 58b76174..166c03ec 100644
--- a/src/ChatPage.cpp
+++ b/src/ChatPage.cpp
@@ -313,7 +313,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
         connect(this,
                 &ChatPage::initializeEmptyViews,
                 view_manager_,
-                &TimelineViewManager::initWithMessages);
+                &TimelineViewManager::initializeRoomlist);
         connect(this,
                 &ChatPage::initializeMentions,
                 user_mentions_popup_,
@@ -554,7 +554,7 @@ ChatPage::loadStateFromCache()
         try {
                 olm::client()->load(cache::restoreOlmAccount(), STORAGE_SECRET_KEY);
 
-                emit initializeEmptyViews(cache::client()->roomIds());
+                emit initializeEmptyViews();
                 emit initializeRoomList(cache::roomInfo());
                 emit initializeMentions(cache::getTimelineMentions());
                 emit syncTags(cache::roomInfo().toStdMap());
diff --git a/src/ChatPage.h b/src/ChatPage.h
index 84e7cdff..eb60047d 100644
--- a/src/ChatPage.h
+++ b/src/ChatPage.h
@@ -147,7 +147,7 @@ signals:
 
         void initializeRoomList(QMap<QString, RoomInfo>);
         void initializeViews(const mtx::responses::Rooms &rooms);
-        void initializeEmptyViews(const std::vector<QString> &roomIds);
+        void initializeEmptyViews();
         void initializeMentions(const QMap<QString, mtx::responses::Notifications> &notifs);
         void syncUI(const mtx::responses::Rooms &rooms);
         void syncRoomlist(const std::map<QString, RoomInfo> &updates);
diff --git a/src/RoomList.cpp b/src/RoomList.cpp
index 5c41a7a1..5839c4a0 100644
--- a/src/RoomList.cpp
+++ b/src/RoomList.cpp
@@ -183,7 +183,7 @@ RoomList::initialize(const QMap<QString, RoomInfo> &info)
 }
 
 void
-RoomList::cleanupInvites(const std::map<QString, bool> &invites)
+RoomList::cleanupInvites(const QHash<QString, RoomInfo> &invites)
 {
         if (invites.size() == 0)
                 return;
diff --git a/src/RoomList.h b/src/RoomList.h
index 74152c55..af792fd7 100644
--- a/src/RoomList.h
+++ b/src/RoomList.h
@@ -48,7 +48,7 @@ public:
         //! Show all the available rooms.
         void removeFilter(const std::set<QString> &roomsToHide);
         void updateRoom(const QString &room_id, const RoomInfo &info);
-        void cleanupInvites(const std::map<QString, bool> &invites);
+        void cleanupInvites(const QHash<QString, RoomInfo> &invites);
 
 signals:
         void roomChanged(const QString &room_id);
diff --git a/src/timeline/RoomlistModel.cpp b/src/timeline/RoomlistModel.cpp
index 28c3cf46..f3d4dad7 100644
--- a/src/timeline/RoomlistModel.cpp
+++ b/src/timeline/RoomlistModel.cpp
@@ -57,31 +57,64 @@ RoomlistModel::data(const QModelIndex &index, int role) const
 {
         if (index.row() >= 0 && static_cast<size_t>(index.row()) < roomids.size()) {
                 auto roomid = roomids.at(index.row());
-                auto room   = models.value(roomid);
-                switch (role) {
-                case Roles::AvatarUrl:
-                        return room->roomAvatarUrl();
-                case Roles::RoomName:
-                        return room->plainRoomName();
-                case Roles::RoomId:
-                        return room->roomId();
-                case Roles::LastMessage:
-                        return room->lastMessage().body;
-                case Roles::Time:
-                        return room->lastMessage().descriptiveTime;
-                case Roles::Timestamp:
-                        return QVariant(static_cast<quint64>(room->lastMessage().timestamp));
-                case Roles::HasUnreadMessages:
-                        return this->roomReadStatus.count(roomid) &&
-                               this->roomReadStatus.at(roomid);
-                case Roles::HasLoudNotification:
-                        return room->hasMentions();
-                case Roles::NotificationCount:
-                        return room->notificationCount();
-                case Roles::IsInvite:
-                case Roles::IsSpace:
-                        return false;
-                default:
+
+                if (models.contains(roomid)) {
+                        auto room = models.value(roomid);
+                        switch (role) {
+                        case Roles::AvatarUrl:
+                                return room->roomAvatarUrl();
+                        case Roles::RoomName:
+                                return room->plainRoomName();
+                        case Roles::RoomId:
+                                return room->roomId();
+                        case Roles::LastMessage:
+                                return room->lastMessage().body;
+                        case Roles::Time:
+                                return room->lastMessage().descriptiveTime;
+                        case Roles::Timestamp:
+                                return QVariant(
+                                  static_cast<quint64>(room->lastMessage().timestamp));
+                        case Roles::HasUnreadMessages:
+                                return this->roomReadStatus.count(roomid) &&
+                                       this->roomReadStatus.at(roomid);
+                        case Roles::HasLoudNotification:
+                                return room->hasMentions();
+                        case Roles::NotificationCount:
+                                return room->notificationCount();
+                        case Roles::IsInvite:
+                        case Roles::IsSpace:
+                                return false;
+                        default:
+                                return {};
+                        }
+                } else if (invites.contains(roomid)) {
+                        auto room = invites.value(roomid);
+                        switch (role) {
+                        case Roles::AvatarUrl:
+                                return QString::fromStdString(room.avatar_url);
+                        case Roles::RoomName:
+                                return QString::fromStdString(room.name);
+                        case Roles::RoomId:
+                                return roomid;
+                        case Roles::LastMessage:
+                                return room.msgInfo.body;
+                        case Roles::Time:
+                                return room.msgInfo.descriptiveTime;
+                        case Roles::Timestamp:
+                                return QVariant(static_cast<quint64>(room.msgInfo.timestamp));
+                        case Roles::HasUnreadMessages:
+                        case Roles::HasLoudNotification:
+                                return false;
+                        case Roles::NotificationCount:
+                                return 0;
+                        case Roles::IsInvite:
+                                return true;
+                        case Roles::IsSpace:
+                                return false;
+                        default:
+                                return {};
+                        }
+                } else {
                         return {};
                 }
         } else {
@@ -109,7 +142,7 @@ RoomlistModel::updateReadStatus(const std::map<QString, bool> roomReadStatus_)
                                    Roles::HasUnreadMessages,
                                  });
         }
-};
+}
 void
 RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
 {
@@ -186,11 +219,21 @@ RoomlistModel::addRoom(const QString &room_id, bool suppressInsertNotification)
 
                 newRoom->updateLastMessage();
 
-                if (!suppressInsertNotification)
+                bool wasInvite = invites.contains(room_id);
+                if (!suppressInsertNotification && !wasInvite)
                         beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size());
+
                 models.insert(room_id, std::move(newRoom));
-                roomids.push_back(room_id);
-                if (!suppressInsertNotification)
+
+                if (wasInvite) {
+                        auto idx = roomidToIndex(room_id);
+                        invites.remove(room_id);
+                        emit dataChanged(index(idx), index(idx));
+                } else {
+                        roomids.push_back(room_id);
+                }
+
+                if (!suppressInsertNotification && !wasInvite)
                         endInsertRows();
         }
 }
@@ -234,20 +277,50 @@ RoomlistModel::sync(const mtx::responses::Rooms &rooms)
                 if (idx != -1) {
                         beginRemoveRows(QModelIndex(), idx, idx);
                         roomids.erase(roomids.begin() + idx);
-                        models.remove(QString::fromStdString(room_id));
+                        if (models.contains(QString::fromStdString(room_id)))
+                                models.remove(QString::fromStdString(room_id));
+                        else if (invites.contains(QString::fromStdString(room_id)))
+                                invites.remove(QString::fromStdString(room_id));
                         endRemoveRows();
                 }
         }
+
+        for (const auto &[room_id, room] : rooms.invite) {
+                (void)room_id;
+                auto qroomid = QString::fromStdString(room_id);
+
+                auto invite = cache::client()->invite(room_id);
+                if (!invite)
+                        continue;
+
+                if (invites.contains(qroomid)) {
+                        invites[qroomid] = *invite;
+                        auto idx         = roomidToIndex(qroomid);
+                        emit dataChanged(index(idx), index(idx));
+                } else {
+                        beginInsertRows(QModelIndex(), (int)roomids.size(), (int)roomids.size());
+                        invites.insert(qroomid, *invite);
+                        roomids.push_back(std::move(qroomid));
+                        endInsertRows();
+                }
+        }
 }
 
 void
-RoomlistModel::initializeRooms(const std::vector<QString> &roomIds_)
+RoomlistModel::initializeRooms()
 {
         beginResetModel();
         models.clear();
         roomids.clear();
-        for (const auto &id : roomIds_)
+        invites.clear();
+
+        invites = cache::client()->invites();
+        for (const auto &id : invites.keys())
+                roomids.push_back(id);
+
+        for (const auto &id : cache::client()->roomIds())
                 addRoom(id, true);
+
         endResetModel();
 }
 
@@ -256,10 +329,42 @@ RoomlistModel::clear()
 {
         beginResetModel();
         models.clear();
+        invites.clear();
         roomids.clear();
         endResetModel();
 }
 
+void
+RoomlistModel::acceptInvite(QString roomid)
+{
+        if (invites.contains(roomid)) {
+                auto idx = roomidToIndex(roomid);
+
+                if (idx != -1) {
+                        beginRemoveRows(QModelIndex(), idx, idx);
+                        roomids.erase(roomids.begin() + idx);
+                        invites.remove(roomid);
+                        endRemoveRows();
+                        ChatPage::instance()->joinRoom(roomid);
+                }
+        }
+}
+void
+RoomlistModel::declineInvite(QString roomid)
+{
+        if (invites.contains(roomid)) {
+                auto idx = roomidToIndex(roomid);
+
+                if (idx != -1) {
+                        beginRemoveRows(QModelIndex(), idx, idx);
+                        roomids.erase(roomids.begin() + idx);
+                        invites.remove(roomid);
+                        endRemoveRows();
+                        ChatPage::instance()->leaveRoom(roomid);
+                }
+        }
+}
+
 namespace {
 enum NotificationImportance : short
 {
diff --git a/src/timeline/RoomlistModel.h b/src/timeline/RoomlistModel.h
index c3374bd2..ff85614c 100644
--- a/src/timeline/RoomlistModel.h
+++ b/src/timeline/RoomlistModel.h
@@ -4,6 +4,7 @@
 
 #pragma once
 
+#include <CacheStructs.h>
 #include <QAbstractListModel>
 #include <QHash>
 #include <QSharedPointer>
@@ -51,7 +52,7 @@ public:
         }
 
 public slots:
-        void initializeRooms(const std::vector<QString> &roomids);
+        void initializeRooms();
         void sync(const mtx::responses::Rooms &rooms);
         void clear();
         int roomidToIndex(QString roomid)
@@ -63,6 +64,8 @@ public slots:
 
                 return -1;
         }
+        void acceptInvite(QString roomid);
+        void declineInvite(QString roomid);
 
 private slots:
         void updateReadStatus(const std::map<QString, bool> roomReadStatus_);
@@ -75,6 +78,7 @@ private:
 
         TimelineViewManager *manager = nullptr;
         std::vector<QString> roomids;
+        QHash<QString, RoomInfo> invites;
         QHash<QString, QSharedPointer<TimelineModel>> models;
         std::map<QString, bool> roomReadStatus;
 
@@ -94,6 +98,8 @@ public slots:
                 return mapFromSource(roomlistmodel->index(roomlistmodel->roomidToIndex(roomid)))
                   .row();
         }
+        void acceptInvite(QString roomid) { roomlistmodel->acceptInvite(roomid); }
+        void declineInvite(QString roomid) { roomlistmodel->declineInvite(roomid); }
 
 private:
         short int calculateImportance(const QModelIndex &idx) const;
diff --git a/src/timeline/TimelineViewManager.cpp b/src/timeline/TimelineViewManager.cpp
index c84e0df8..9fa7f8b6 100644
--- a/src/timeline/TimelineViewManager.cpp
+++ b/src/timeline/TimelineViewManager.cpp
@@ -499,9 +499,9 @@ TimelineViewManager::receivedSessionKey(const std::string &room_id, const std::s
 }
 
 void
-TimelineViewManager::initWithMessages(const std::vector<QString> &roomIds)
+TimelineViewManager::initializeRoomlist()
 {
-        rooms->initializeRooms(roomIds);
+        rooms->initializeRooms();
 }
 
 void
diff --git a/src/timeline/TimelineViewManager.h b/src/timeline/TimelineViewManager.h
index 609f5a4a..37e50804 100644
--- a/src/timeline/TimelineViewManager.h
+++ b/src/timeline/TimelineViewManager.h
@@ -100,7 +100,7 @@ signals:
 public slots:
         void updateReadReceipts(const QString &room_id, const std::vector<QString> &event_ids);
         void receivedSessionKey(const std::string &room_id, const std::string &session_id);
-        void initWithMessages(const std::vector<QString> &roomIds);
+        void initializeRoomlist();
         void chatFocusChanged(bool focused)
         {
                 isWindowFocused_ = focused;
diff --git a/src/ui/Theme.cpp b/src/ui/Theme.cpp
index b6c9579a..26119393 100644
--- a/src/ui/Theme.cpp
+++ b/src/ui/Theme.cpp
@@ -60,12 +60,15 @@ Theme::Theme(std::string_view theme)
         separator_ = p.mid().color();
         if (theme == "light") {
                 sidebarBackground_ = QColor("#233649");
+                alternateButton_   = QColor("#ccc");
                 red_               = QColor("#a82353");
         } else if (theme == "dark") {
                 sidebarBackground_ = QColor("#2d3139");
+                alternateButton_   = QColor("#414A59");
                 red_               = QColor("#a82353");
         } else {
                 sidebarBackground_ = p.window().color();
+                alternateButton_   = p.dark().color();
                 red_               = QColor("red");
         }
 }
diff --git a/src/ui/Theme.h b/src/ui/Theme.h
index 834571c0..b5bcd4dd 100644
--- a/src/ui/Theme.h
+++ b/src/ui/Theme.h
@@ -65,6 +65,7 @@ class Theme : public QPalette
 {
         Q_GADGET
         Q_PROPERTY(QColor sidebarBackground READ sidebarBackground CONSTANT)
+        Q_PROPERTY(QColor alternateButton READ alternateButton CONSTANT)
         Q_PROPERTY(QColor separator READ separator CONSTANT)
         Q_PROPERTY(QColor red READ red CONSTANT)
 public:
@@ -73,9 +74,10 @@ public:
         static QPalette paletteFromTheme(std::string_view theme);
 
         QColor sidebarBackground() const { return sidebarBackground_; }
+        QColor alternateButton() const { return alternateButton_; }
         QColor separator() const { return separator_; }
         QColor red() const { return red_; }
 
 private:
-        QColor sidebarBackground_, separator_, red_;
+        QColor sidebarBackground_, separator_, red_, alternateButton_;
 };