Show spaces horizontal bar

### Summary

This merge request adds a horizontal bar at top of room list, which shows spaces. By clicking on a space, user can filter out rooms belonging only to that specific space.

### Pending/ Help needed

#### Segfault when loading active connection on startup

Refer `void SortFilterRoomListModel::cacheSpaceHierarchy()` ([link](8c372800d7 (b969e462c30df43ef3714ea441948d8d8027f6a0_117_126))) in `src/sortfilterroomlistmodel.cpp`.

On [line 129](8c372800d7 (b969e462c30df43ef3714ea441948d8d8027f6a0_117_129)), I have called `connection->allRooms()`, which segfaults if the active connection hasn't been loaded yet. Is there a way to ensure that `Controller::instance().activeConnection()` on line 128 waits till connection is loaded?

#### Avatars

Avatars on space horizontal bar aren't aligned to vertical middle. I'll need help with that. 

Using the code below doesn't help with padding

```qml
delegate: QQC2.Control {
    topPadding: 10
    contentItem: Kirigami.Avatar { ..... }
}
```

This complains about uninitialized properties in `Kirigami.Avatar`. (`id`, `currentRoom`, `avatar`, `index` are properties utilized during run time)

After we get around these two issue, this MR will be ready from my side.
This commit is contained in:
Snehit Sah
2022-08-25 13:46:09 +00:00
committed by Carl Schwan
parent cd895f1b06
commit 91d1f6ffeb
13 changed files with 298 additions and 3 deletions

View File

@@ -14,6 +14,8 @@ add_library(neochat STATIC
messageeventmodel.cpp
messagefiltermodel.cpp
roomlistmodel.cpp
sortfilterspacelistmodel.cpp
spacehierarchycache.cpp
roommanager.cpp
neochatroom.cpp
neochatuser.cpp

View File

@@ -69,6 +69,8 @@
#include "roomlistmodel.h"
#include "roommanager.h"
#include "sortfilterroomlistmodel.h"
#include "sortfilterspacelistmodel.h"
#include "spacehierarchycache.h"
#include "urlhelper.h"
#include "userdirectorylistmodel.h"
#include "userlistmodel.h"
@@ -177,6 +179,7 @@ int main(int argc, char *argv[])
Login *login = new Login();
ChatBoxHelper chatBoxHelper;
UrlHelper urlHelper;
SpaceHierarchyCache spaceHierarchyCache;
#ifdef HAVE_COLORSCHEME
ColorSchemer colorScheme;
@@ -197,6 +200,7 @@ int main(int argc, char *argv[])
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", new EmojiModel(&app));
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CommandModel", new CommandModel(&app));
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Quotient::AccountRegistry::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "SpaceHierarchyCache", &spaceHierarchyCache);
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
qmlRegisterType<ChatDocumentHandler>("org.kde.neochat", 1, 0, "ChatDocumentHandler");
qmlRegisterType<RoomListModel>("org.kde.neochat", 1, 0, "RoomListModel");
@@ -209,6 +213,7 @@ int main(int argc, char *argv[])
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
qmlRegisterType<SortFilterRoomListModel>("org.kde.neochat", 1, 0, "SortFilterRoomListModel");
qmlRegisterType<SortFilterSpaceListModel>("org.kde.neochat", 1, 0, "SortFilterSpaceListModel");
qmlRegisterType<DevicesModel>("org.kde.neochat", 1, 0, "DevicesModel");
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");

View File

@@ -30,6 +30,7 @@
#include "events/accountdataevents.h"
#include "events/reactionevent.h"
#include "events/roomcanonicalaliasevent.h"
#include "events/roomcreateevent.h"
#include "events/roommessageevent.h"
#include "events/roompowerlevelsevent.h"
#include "events/typingevent.h"
@@ -869,3 +870,17 @@ void NeoChatRoom::clearInvitationNotification()
{
NotificationsManager::instance().clearInvitationNotification(id());
}
bool NeoChatRoom::isSpace()
{
const auto creationEvent = this->creation();
if (!creationEvent) {
return false;
}
#ifdef QUOTIENT_07
return creationEvent->roomType() == RoomType::Space;
#else
return false;
#endif
}

View File

@@ -72,6 +72,8 @@ public:
/// \see lastEventToString
[[nodiscard]] QString subtitleText();
[[nodiscard]] bool isSpace();
bool isEventHighlighted(const Quotient::RoomEvent *e) const;
[[nodiscard]] QString joinRule() const;

View File

@@ -408,6 +408,10 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
if (role == IdRole) {
return room->id();
}
if (role == IsSpaceRole) {
return room->isSpace();
}
return QVariant();
}
@@ -439,6 +443,8 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
roles[CurrentRoomRole] = "currentRoom";
roles[CategoryVisibleRole] = "categoryVisible";
roles[SubtitleTextRole] = "subtitleText";
roles[IsSpaceRole] = "isSpace";
roles[IdRole] = "id";
return roles;
}

View File

@@ -52,6 +52,7 @@ public:
SubtitleTextRole,
AvatarImageRole,
IdRole,
IsSpaceRole,
};
Q_ENUM(EventRoles)
@@ -99,6 +100,7 @@ private:
QMap<int, bool> m_categoryVisibility;
int m_notificationCount = 0;
QString m_activeSpaceId = "";
void connectRoomSignals(NeoChatRoom *room);
void handleNotifications();

View File

@@ -76,6 +76,23 @@ QString SortFilterRoomListModel::filterText() const
bool SortFilterRoomListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent);
return sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::NameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded";
bool acceptRoom = sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::NameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded"
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() == false;
if (m_activeSpaceRooms.empty())
return acceptRoom;
else
return std::find(m_activeSpaceRooms.begin(),
m_activeSpaceRooms.end(),
sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IdRole).toString())
!= m_activeSpaceRooms.end()
&& acceptRoom;
}
void SortFilterRoomListModel::setActiveSpaceRooms(QVector<QString> activeSpaceRooms)
{
this->m_activeSpaceRooms = activeSpaceRooms;
invalidate();
}

View File

@@ -11,6 +11,7 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
Q_PROPERTY(RoomSortOrder roomSortOrder READ roomSortOrder WRITE setRoomSortOrder NOTIFY roomSortOrderChanged)
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
Q_PROPERTY(QVector<QString> activeSpaceRooms WRITE setActiveSpaceRooms)
public:
enum RoomSortOrder {
@@ -30,6 +31,8 @@ public:
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
Q_INVOKABLE void setActiveSpaceRooms(QVector<QString> activeSpaceRooms);
protected:
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
@@ -40,4 +43,5 @@ Q_SIGNALS:
private:
RoomSortOrder m_sortOrder = Categories;
QString m_filterText;
QVector<QString> m_activeSpaceRooms;
};

View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#include "sortfilterspacelistmodel.h"
#include "roomlistmodel.h"
SortFilterSpaceListModel::SortFilterSpaceListModel(QObject *parent)
: QSortFilterProxyModel{parent}
{
setSortRole(RoomListModel::IdRole);
sort(0);
invalidateFilter();
}
bool SortFilterSpaceListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent);
return sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool()
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded";
}
bool SortFilterSpaceListModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
{
const auto idLeft = sourceModel()->data(source_left, RoomListModel::IdRole).toString();
const auto idRight = sourceModel()->data(source_right, RoomListModel::IdRole).toString();
return idLeft < idRight;
}

View File

@@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#pragma once
#include <QSortFilterProxyModel>
class SortFilterSpaceListModel : public QSortFilterProxyModel
{
Q_OBJECT
public:
explicit SortFilterSpaceListModel(QObject *parent = nullptr);
[[nodiscard]] QString activeSpaceId() const;
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
Q_SIGNALS:
void activeSpaceIdChanged(QString &activeSpaceId);
protected:
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
};

View File

@@ -0,0 +1,72 @@
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#include "spacehierarchycache.h"
#include "controller.h"
#ifdef QUOTIENT_07
#include "csapi/space_hierarchy.h"
#endif
#include "neochatroom.h"
SpaceHierarchyCache::SpaceHierarchyCache(QObject *parent)
: QObject{parent}
{
cacheSpaceHierarchy();
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
cacheSpaceHierarchy();
});
}
void SpaceHierarchyCache::cacheSpaceHierarchy()
{
#ifdef QUOTIENT_07
auto connection = Controller::instance().activeConnection();
if (!connection) {
return;
}
const auto roomList = connection->allRooms();
for (const auto &room : roomList) {
const auto neoChatRoom = static_cast<NeoChatRoom *>(room);
if (neoChatRoom->isSpace()) {
populateSpaceHierarchy(neoChatRoom->id());
} else {
connect(neoChatRoom, &Room::baseStateLoaded, neoChatRoom, [this, neoChatRoom]() {
if (neoChatRoom->isSpace()) {
populateSpaceHierarchy(neoChatRoom->id());
}
});
}
}
#endif
}
void SpaceHierarchyCache::populateSpaceHierarchy(const QString &spaceId)
{
auto connection = Controller::instance().activeConnection();
if (!connection) {
return;
}
#ifdef QUOTIENT_07
GetSpaceHierarchyJob *job = connection->callApi<GetSpaceHierarchyJob>(spaceId);
connect(job, &BaseJob::success, this, [this, job, spaceId]() {
const auto rooms = job->rooms();
QVector<QString> roomList;
for (unsigned long i = 0; i < rooms.size(); ++i) {
roomList.push_back(rooms.at(i).roomId);
}
m_spaceHierarchy.insert(spaceId, roomList);
Q_EMIT spaceHierarchyChanged();
});
#endif
}
QVector<QString> SpaceHierarchyCache::getRoomListForSpace(const QString &spaceId, bool updateCache)
{
if (updateCache) {
populateSpaceHierarchy(spaceId);
}
return m_spaceHierarchy[spaceId];
}

28
src/spacehierarchycache.h Normal file
View File

@@ -0,0 +1,28 @@
// SPDX-FileCopyrightText: 2022 Snehit Sah <hi@snehit.dev>
// SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
#pragma once
#include <QHash>
#include <QObject>
#include <QString>
#include <QVector>
class SpaceHierarchyCache : public QObject
{
Q_OBJECT
public:
explicit SpaceHierarchyCache(QObject *parent = nullptr);
[[nodiscard]] Q_INVOKABLE QVector<QString> getRoomListForSpace(const QString &spaceId, bool updateCache);
Q_SIGNALS:
void spaceHierarchyChanged();
private:
QVector<QString> m_activeSpaceRooms;
QHash<QString, QVector<QString>> m_spaceHierarchy;
void cacheSpaceHierarchy();
void populateSpaceHierarchy(const QString &spaceId);
};