Space Home Page
Add a space homepage with the ability to both create new room and add existing rooms to the space. This uses a tree model for the space hierarchy and will go to any number of levels. The user should only see the add options if they have appropriate permissions. This MR also combines the create space and room pages and adds a lot of optional functionality for managing space children. 
This commit is contained in:
@@ -40,6 +40,12 @@ add_library(neochat STATIC
|
||||
models/userfiltermodel.h
|
||||
models/publicroomlistmodel.cpp
|
||||
models/publicroomlistmodel.h
|
||||
models/spacechildrenmodel.cpp
|
||||
models/spacechildrenmodel.h
|
||||
models/spacechildsortfiltermodel.cpp
|
||||
models/spacechildsortfiltermodel.h
|
||||
models/spacetreeitem.cpp
|
||||
models/spacetreeitem.h
|
||||
models/userdirectorylistmodel.cpp
|
||||
models/userdirectorylistmodel.h
|
||||
models/pushrulemodel.cpp
|
||||
@@ -209,7 +215,6 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/Sso.qml
|
||||
qml/UserDetailDialog.qml
|
||||
qml/CreateRoomDialog.qml
|
||||
qml/CreateSpaceDialog.qml
|
||||
qml/EmojiDialog.qml
|
||||
qml/OpenFileDialog.qml
|
||||
qml/KeyVerificationDialog.qml
|
||||
@@ -273,6 +278,8 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/RoomMedia.qml
|
||||
qml/ChooseRoomDialog.qml
|
||||
qml/ShareAction.qml
|
||||
qml/SpaceHomePage.qml
|
||||
qml/SpaceHierarchyDelegate.qml
|
||||
RESOURCES
|
||||
qml/confetti.png
|
||||
qml/glowdot.png
|
||||
|
||||
289
src/models/spacechildrenmodel.cpp
Normal file
289
src/models/spacechildrenmodel.cpp
Normal file
@@ -0,0 +1,289 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "spacechildrenmodel.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
{
|
||||
m_rootItem = new SpaceTreeItem();
|
||||
}
|
||||
|
||||
SpaceChildrenModel::~SpaceChildrenModel()
|
||||
{
|
||||
delete m_rootItem;
|
||||
}
|
||||
|
||||
NeoChatRoom *SpaceChildrenModel::space() const
|
||||
{
|
||||
return m_space;
|
||||
}
|
||||
|
||||
void SpaceChildrenModel::setSpace(NeoChatRoom *space)
|
||||
{
|
||||
if (space == m_space) {
|
||||
return;
|
||||
}
|
||||
// disconnect the new room signal from the old connection in case it is different.
|
||||
if (m_space != nullptr) {
|
||||
disconnect(m_space->connection(), &Quotient::Connection::loadedRoomState, this, nullptr);
|
||||
}
|
||||
|
||||
m_space = space;
|
||||
Q_EMIT spaceChanged();
|
||||
|
||||
for (auto job : m_currentJobs) {
|
||||
if (job) {
|
||||
job->abandon();
|
||||
}
|
||||
}
|
||||
m_currentJobs.clear();
|
||||
|
||||
auto connection = m_space->connection();
|
||||
connect(connection, &Quotient::Connection::loadedRoomState, this, [this](Quotient::Room *room) {
|
||||
if (m_pendingChildren.contains(room->name())) {
|
||||
m_pendingChildren.removeAll(room->name());
|
||||
refreshModel();
|
||||
}
|
||||
});
|
||||
connect(m_space, &Quotient::Room::changed, this, [this]() {
|
||||
refreshModel();
|
||||
});
|
||||
|
||||
refreshModel();
|
||||
}
|
||||
|
||||
bool SpaceChildrenModel::loading() const
|
||||
{
|
||||
return m_loading;
|
||||
}
|
||||
|
||||
void SpaceChildrenModel::refreshModel()
|
||||
{
|
||||
beginResetModel();
|
||||
m_replacedRooms.clear();
|
||||
delete m_rootItem;
|
||||
m_loading = true;
|
||||
Q_EMIT loadingChanged();
|
||||
m_rootItem = new SpaceTreeItem();
|
||||
endResetModel();
|
||||
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(m_space->id(), Quotient::none, Quotient::none, 1);
|
||||
m_currentJobs.append(job);
|
||||
connect(job, &Quotient::BaseJob::success, this, [this, job]() {
|
||||
insertChildren(job->rooms());
|
||||
});
|
||||
}
|
||||
|
||||
void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent)
|
||||
{
|
||||
SpaceTreeItem *parentItem = getItem(parent);
|
||||
|
||||
if (children[0].roomId == m_space->id() || children[0].roomId == parentItem->id()) {
|
||||
children.erase(children.begin());
|
||||
}
|
||||
|
||||
// If this is the first set of children added to the root item then we need to
|
||||
// set it so that we are no longer loading.
|
||||
if (rowCount(QModelIndex()) == 0 && !children.empty()) {
|
||||
m_loading = false;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
beginInsertRows(parent, parentItem->childCount(), parentItem->childCount() + children.size() - 1);
|
||||
for (unsigned long i = 0; i < children.size(); ++i) {
|
||||
if (children[i].roomId == m_space->id() || children[i].roomId == parentItem->id()) {
|
||||
continue;
|
||||
} else {
|
||||
int insertRow = parentItem->childCount();
|
||||
if (const auto room = m_space->connection()->room(children[i].roomId)) {
|
||||
const auto predecessorId = room->predecessorId();
|
||||
if (!predecessorId.isEmpty()) {
|
||||
m_replacedRooms += predecessorId;
|
||||
}
|
||||
const auto successorId = room->successorId();
|
||||
if (!successorId.isEmpty()) {
|
||||
m_replacedRooms += successorId;
|
||||
}
|
||||
}
|
||||
parentItem->insertChild(insertRow,
|
||||
new SpaceTreeItem(parentItem,
|
||||
children[i].roomId,
|
||||
children[i].name,
|
||||
children[i].canonicalAlias,
|
||||
children[i].topic,
|
||||
children[i].numJoinedMembers,
|
||||
children[i].avatarUrl,
|
||||
children[i].guestCanJoin,
|
||||
children[i].worldReadable,
|
||||
children[i].roomType == QLatin1String("m.space")));
|
||||
if (children[i].childrenState.size() > 0) {
|
||||
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, Quotient::none, Quotient::none, 1);
|
||||
m_currentJobs.append(job);
|
||||
connect(job, &Quotient::BaseJob::success, this, [this, parent, insertRow, job]() {
|
||||
insertChildren(job->rooms(), index(insertRow, 0, parent));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
SpaceTreeItem *SpaceChildrenModel::getItem(const QModelIndex &index) const
|
||||
{
|
||||
if (index.isValid()) {
|
||||
SpaceTreeItem *item = static_cast<SpaceTreeItem *>(index.internalPointer());
|
||||
if (item) {
|
||||
return item;
|
||||
}
|
||||
}
|
||||
return m_rootItem;
|
||||
}
|
||||
|
||||
QVariant SpaceChildrenModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
SpaceTreeItem *child = getItem(index);
|
||||
if (role == DisplayNameRole) {
|
||||
auto displayName = child->name();
|
||||
if (!displayName.isEmpty()) {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
displayName = child->canonicalAlias();
|
||||
if (!displayName.isEmpty()) {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
return child->id();
|
||||
}
|
||||
if (role == AvatarUrlRole) {
|
||||
return child->avatarUrl();
|
||||
}
|
||||
if (role == TopicRole) {
|
||||
return child->topic();
|
||||
}
|
||||
if (role == RoomIDRole) {
|
||||
return child->id();
|
||||
}
|
||||
if (role == AliasRole) {
|
||||
return child->canonicalAlias();
|
||||
}
|
||||
if (role == MemberCountRole) {
|
||||
return child->memberCount();
|
||||
}
|
||||
if (role == AllowGuestsRole) {
|
||||
return child->allowGuests();
|
||||
}
|
||||
if (role == WorldReadableRole) {
|
||||
return child->worldReadable();
|
||||
}
|
||||
if (role == IsJoinedRole) {
|
||||
return child->isJoined();
|
||||
}
|
||||
if (role == IsSpaceRole) {
|
||||
return child->isSpace();
|
||||
}
|
||||
if (role == CanAddChildrenRole) {
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
if (const auto room = static_cast<NeoChatRoom *>(connection->room(child->id()))) {
|
||||
return room->canSendState(QLatin1String("m.space.child"));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QModelIndex SpaceChildrenModel::index(int row, int column, const QModelIndex &parent) const
|
||||
{
|
||||
if (!hasIndex(row, column, parent)) {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
SpaceTreeItem *parentItem = getItem(parent);
|
||||
if (!parentItem) {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
SpaceTreeItem *childItem = parentItem->child(row);
|
||||
if (childItem) {
|
||||
return createIndex(row, column, childItem);
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
QModelIndex SpaceChildrenModel::parent(const QModelIndex &index) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
SpaceTreeItem *childItem = static_cast<SpaceTreeItem *>(index.internalPointer());
|
||||
SpaceTreeItem *parentItem = childItem->parentItem();
|
||||
|
||||
if (parentItem == m_rootItem) {
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
return createIndex(parentItem->row(), 0, parentItem);
|
||||
}
|
||||
|
||||
int SpaceChildrenModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
SpaceTreeItem *parentItem;
|
||||
if (parent.column() > 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (!parent.isValid()) {
|
||||
parentItem = m_rootItem;
|
||||
} else {
|
||||
parentItem = static_cast<SpaceTreeItem *>(parent.internalPointer());
|
||||
}
|
||||
|
||||
return parentItem->childCount();
|
||||
}
|
||||
|
||||
int SpaceChildrenModel::columnCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> SpaceChildrenModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
|
||||
roles[DisplayNameRole] = "displayName";
|
||||
roles[AvatarUrlRole] = "avatarUrl";
|
||||
roles[TopicRole] = "topic";
|
||||
roles[RoomIDRole] = "roomId";
|
||||
roles[MemberCountRole] = "memberCount";
|
||||
roles[AllowGuestsRole] = "allowGuests";
|
||||
roles[WorldReadableRole] = "worldReadable";
|
||||
roles[IsJoinedRole] = "isJoined";
|
||||
roles[AliasRole] = "alias";
|
||||
roles[IsSpaceRole] = "isSpace";
|
||||
roles[CanAddChildrenRole] = "canAddChildren";
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
bool SpaceChildrenModel::isRoomReplaced(const QString &roomId) const
|
||||
{
|
||||
return m_replacedRooms.contains(roomId);
|
||||
}
|
||||
|
||||
void SpaceChildrenModel::addPendingChild(const QString &childName)
|
||||
{
|
||||
m_pendingChildren += childName;
|
||||
}
|
||||
139
src/models/spacechildrenmodel.h
Normal file
139
src/models/spacechildrenmodel.h
Normal file
@@ -0,0 +1,139 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <Quotient/csapi/space_hierarchy.h>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
#include "neochatroom.h"
|
||||
#include "spacetreeitem.h"
|
||||
|
||||
/**
|
||||
* @class SpaceChildrenModel
|
||||
*
|
||||
* Create a model that contains a list of the child rooms for any given space id.
|
||||
*/
|
||||
class SpaceChildrenModel : public QAbstractItemModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The current space that the hierarchy is being generated for.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *space READ space WRITE setSpace NOTIFY spaceChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the model is loading the initial set of children.
|
||||
*/
|
||||
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
DisplayNameRole = Qt::DisplayRole,
|
||||
AvatarUrlRole,
|
||||
TopicRole,
|
||||
RoomIDRole,
|
||||
AliasRole,
|
||||
MemberCountRole,
|
||||
AllowGuestsRole,
|
||||
WorldReadableRole,
|
||||
IsJoinedRole,
|
||||
IsSpaceRole,
|
||||
CanAddChildrenRole,
|
||||
};
|
||||
|
||||
explicit SpaceChildrenModel(QObject *parent = nullptr);
|
||||
~SpaceChildrenModel();
|
||||
|
||||
NeoChatRoom *space() const;
|
||||
void setSpace(NeoChatRoom *space);
|
||||
|
||||
bool loading() const;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role = DisplayNameRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns the index of the item in the model specified by the given row, column and parent index.
|
||||
*
|
||||
* @sa QAbstractItemModel::index
|
||||
*/
|
||||
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns the parent of the model item with the given index.
|
||||
*
|
||||
* If the item has no parent, an invalid QModelIndex is returned.
|
||||
*
|
||||
* @sa QAbstractItemModel::parent
|
||||
*/
|
||||
QModelIndex parent(const QModelIndex &index) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of columns in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::columnCount
|
||||
*/
|
||||
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Whether the room has been replaced.
|
||||
*
|
||||
* @note This information is only available if the local user is either a member
|
||||
* of the replaced room or is a member of the successor room as currently
|
||||
* there is no other way to obtain the required information.
|
||||
*/
|
||||
bool isRoomReplaced(const QString &roomId) const;
|
||||
|
||||
/**
|
||||
* @brief Add the name of new child room that is expected to be added soon.
|
||||
*
|
||||
* A pending child is one where Quotient::Connection::createRoom has been called
|
||||
* but the room hasn't synced with the server yet. This list is used to check
|
||||
* whether a new room loading should trigger a refresh of the model, as we only
|
||||
* want to trigger a refresh if the loading room is part of this space.
|
||||
*/
|
||||
Q_INVOKABLE void addPendingChild(const QString &childName);
|
||||
|
||||
Q_SIGNALS:
|
||||
void spaceChanged();
|
||||
void loadingChanged();
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_space = nullptr;
|
||||
SpaceTreeItem *m_rootItem;
|
||||
|
||||
bool m_loading = false;
|
||||
QList<QPointer<Quotient::GetSpaceHierarchyJob>> m_currentJobs;
|
||||
QList<QString> m_pendingChildren;
|
||||
|
||||
QList<QString> m_replacedRooms;
|
||||
|
||||
SpaceTreeItem *getItem(const QModelIndex &index) const;
|
||||
|
||||
void refreshModel();
|
||||
void insertChildren(std::vector<Quotient::GetSpaceHierarchyJob::ChildRoomsChunk> children, const QModelIndex &parent = QModelIndex());
|
||||
};
|
||||
46
src/models/spacechildsortfiltermodel.cpp
Normal file
46
src/models/spacechildsortfiltermodel.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "spacechildsortfiltermodel.h"
|
||||
|
||||
#include "spacechildrenmodel.h"
|
||||
|
||||
SpaceChildSortFilterModel::SpaceChildSortFilterModel(QObject *parent)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
setRecursiveFilteringEnabled(true);
|
||||
sort(0);
|
||||
}
|
||||
|
||||
void SpaceChildSortFilterModel::setFilterText(const QString &filterText)
|
||||
{
|
||||
m_filterText = filterText;
|
||||
Q_EMIT filterTextChanged();
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
QString SpaceChildSortFilterModel::filterText() const
|
||||
{
|
||||
return m_filterText;
|
||||
}
|
||||
|
||||
bool SpaceChildSortFilterModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||
{
|
||||
if (!source_left.data(SpaceChildrenModel::IsSpaceRole).toBool() && source_right.data(SpaceChildrenModel::IsSpaceRole).toBool()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpaceChildSortFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
QModelIndex index = sourceModel()->index(sourceRow, 0, sourceParent);
|
||||
if (auto sourceModel = static_cast<SpaceChildrenModel *>(this->sourceModel())) {
|
||||
bool isReplaced = sourceModel->isRoomReplaced(index.data(SpaceChildrenModel::RoomIDRole).toString());
|
||||
bool acceptRoom = index.data(SpaceChildrenModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive);
|
||||
return !isReplaced && acceptRoom;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#include "moc_spacechildsortfiltermodel.cpp"
|
||||
54
src/models/spacechildsortfiltermodel.h
Normal file
54
src/models/spacechildsortfiltermodel.h
Normal file
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQmlEngine>
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class SpaceChildSortFilterModel
|
||||
*
|
||||
* This class creates a custom QSortFilterProxyModel for filtering and sorting spaces
|
||||
* in a SpaceChildrenModel.
|
||||
*
|
||||
* @sa SpaceChildrenModel
|
||||
*/
|
||||
class SpaceChildSortFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The text to use to filter room names.
|
||||
*/
|
||||
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
public:
|
||||
SpaceChildSortFilterModel(QObject *parent = nullptr);
|
||||
|
||||
void setFilterText(const QString &filterText);
|
||||
[[nodiscard]] QString filterText() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Returns true if the value of source_left is less than source_right.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::lessThan
|
||||
*/
|
||||
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
/**
|
||||
* @brief Custom filter function checking if an event type has been filtered out.
|
||||
*
|
||||
* The filter rejects a row if the room is known been replaced or if a search
|
||||
* string is set it will only return rooms that match.
|
||||
*/
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void filterTextChanged();
|
||||
|
||||
private:
|
||||
QString m_filterText;
|
||||
};
|
||||
140
src/models/spacetreeitem.cpp
Normal file
140
src/models/spacetreeitem.cpp
Normal file
@@ -0,0 +1,140 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "spacetreeitem.h"
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
SpaceTreeItem::SpaceTreeItem(SpaceTreeItem *parent,
|
||||
const QString &id,
|
||||
const QString &name,
|
||||
const QString &canonicalAlias,
|
||||
const QString &topic,
|
||||
int memberCount,
|
||||
const QUrl &avatarUrl,
|
||||
bool allowGuests,
|
||||
bool worldReadable,
|
||||
bool isSpace)
|
||||
: m_parentItem(parent)
|
||||
, m_id(id)
|
||||
, m_name(name)
|
||||
, m_canonicalAlias(canonicalAlias)
|
||||
, m_topic(topic)
|
||||
, m_memberCount(memberCount)
|
||||
, m_avatarUrl(avatarUrl)
|
||||
, m_allowGuests(allowGuests)
|
||||
, m_worldReadable(worldReadable)
|
||||
, m_isSpace(isSpace)
|
||||
{
|
||||
}
|
||||
|
||||
SpaceTreeItem::~SpaceTreeItem()
|
||||
{
|
||||
qDeleteAll(m_children);
|
||||
}
|
||||
|
||||
SpaceTreeItem *SpaceTreeItem::child(int number)
|
||||
{
|
||||
if (number < 0 || number >= m_children.size()) {
|
||||
return nullptr;
|
||||
}
|
||||
return m_children[number];
|
||||
}
|
||||
|
||||
int SpaceTreeItem::childCount() const
|
||||
{
|
||||
return m_children.count();
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::insertChild(int row, SpaceTreeItem *newChild)
|
||||
{
|
||||
if (row < 0 || row > m_children.size()) {
|
||||
return false;
|
||||
}
|
||||
m_children.insert(row, newChild);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::removeChild(int row)
|
||||
{
|
||||
if (row < 0 || row >= m_children.size()) {
|
||||
return false;
|
||||
}
|
||||
delete m_children.takeAt(row);
|
||||
return true;
|
||||
}
|
||||
|
||||
int SpaceTreeItem::row() const
|
||||
{
|
||||
if (m_parentItem) {
|
||||
return m_parentItem->m_children.indexOf(const_cast<SpaceTreeItem *>(this));
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
SpaceTreeItem *SpaceTreeItem::parentItem()
|
||||
{
|
||||
return m_parentItem;
|
||||
}
|
||||
|
||||
QString SpaceTreeItem::id() const
|
||||
{
|
||||
return m_id;
|
||||
}
|
||||
|
||||
QString SpaceTreeItem::name() const
|
||||
{
|
||||
return m_name;
|
||||
}
|
||||
|
||||
QString SpaceTreeItem::canonicalAlias() const
|
||||
{
|
||||
return m_canonicalAlias;
|
||||
}
|
||||
|
||||
QString SpaceTreeItem::topic() const
|
||||
{
|
||||
return m_topic;
|
||||
}
|
||||
|
||||
int SpaceTreeItem::memberCount() const
|
||||
{
|
||||
return m_memberCount;
|
||||
}
|
||||
|
||||
QUrl SpaceTreeItem::avatarUrl() const
|
||||
{
|
||||
if (m_avatarUrl.isEmpty() || m_avatarUrl.scheme() != QLatin1String("mxc")) {
|
||||
return {};
|
||||
}
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
auto url = connection->makeMediaUrl(m_avatarUrl);
|
||||
if (url.scheme() == QLatin1String("mxc")) {
|
||||
return url;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::allowGuests() const
|
||||
{
|
||||
return m_allowGuests;
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::worldReadable() const
|
||||
{
|
||||
return m_worldReadable;
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::isJoined() const
|
||||
{
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
if (!connection) {
|
||||
return false;
|
||||
}
|
||||
return connection->room(id(), Quotient::JoinState::Join) != nullptr;
|
||||
}
|
||||
|
||||
bool SpaceTreeItem::isSpace() const
|
||||
{
|
||||
return m_isSpace;
|
||||
}
|
||||
136
src/models/spacetreeitem.h
Normal file
136
src/models/spacetreeitem.h
Normal file
@@ -0,0 +1,136 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <Quotient/csapi/space_hierarchy.h>
|
||||
|
||||
/**
|
||||
* @class SpaceTreeItem
|
||||
*
|
||||
* This class defines an item in the space tree hierarchy model.
|
||||
*
|
||||
* @note This is separate from Quotient::Room and NeoChatRoom because we don't have
|
||||
* full room information for any room/space the user hasn't joined and we
|
||||
* don't want to create one for ever possible child in a space as that would
|
||||
* be expensive.
|
||||
*
|
||||
* @sa Quotient::Room, NeoChatRoom
|
||||
*/
|
||||
class SpaceTreeItem
|
||||
{
|
||||
public:
|
||||
explicit SpaceTreeItem(SpaceTreeItem *parent = nullptr,
|
||||
const QString &id = {},
|
||||
const QString &name = {},
|
||||
const QString &canonicalAlias = {},
|
||||
const QString &topic = {},
|
||||
int memberCount = {},
|
||||
const QUrl &avatarUrl = {},
|
||||
bool allowGuests = {},
|
||||
bool worldReadable = {},
|
||||
bool isSpace = {});
|
||||
~SpaceTreeItem();
|
||||
|
||||
/**
|
||||
* @brief Return the child at the given row number.
|
||||
*
|
||||
* Nullptr is returned if there is no child at the given row number.
|
||||
*/
|
||||
SpaceTreeItem *child(int number);
|
||||
|
||||
/**
|
||||
* @brief The number of children this item has.
|
||||
*/
|
||||
int childCount() const;
|
||||
|
||||
/**
|
||||
* @brief Insert the given child at the given row number.
|
||||
*/
|
||||
bool insertChild(int row, SpaceTreeItem *newChild);
|
||||
|
||||
/**
|
||||
* @brief Remove the child at the given row number.
|
||||
*
|
||||
* @return True if a child was removed, false if the given row isn't valid.
|
||||
*/
|
||||
bool removeChild(int row);
|
||||
|
||||
/**
|
||||
* @brief Return this item's parent.
|
||||
*/
|
||||
SpaceTreeItem *parentItem();
|
||||
|
||||
/**
|
||||
* @brief Return the row number for this child relative to the parent.
|
||||
*
|
||||
* @return The row value if the child has a parent, 0 otherwise.
|
||||
*/
|
||||
int row() const;
|
||||
|
||||
/**
|
||||
* @brief The ID of the room.
|
||||
*/
|
||||
QString id() const;
|
||||
|
||||
/**
|
||||
* @brief The name of the room, if any.
|
||||
*/
|
||||
QString name() const;
|
||||
|
||||
/**
|
||||
* @brief The canonical alias of the room, if any.
|
||||
*/
|
||||
QString canonicalAlias() const;
|
||||
|
||||
/**
|
||||
* @brief The topic of the room, if any.
|
||||
*/
|
||||
QString topic() const;
|
||||
|
||||
/**
|
||||
* @brief The number of members joined to the room.
|
||||
*/
|
||||
int memberCount() const;
|
||||
|
||||
/**
|
||||
* @brief The URL for the room's avatar, if one is set.
|
||||
*
|
||||
* @return A CS API QUrl.
|
||||
*/
|
||||
QUrl avatarUrl() const;
|
||||
|
||||
/**
|
||||
* @brief Whether guest users may join the room and participate in it.
|
||||
*
|
||||
* If they can, they will be subject to ordinary power level rules like any other users.
|
||||
*/
|
||||
bool allowGuests() const;
|
||||
|
||||
/**
|
||||
* @brief Whether the room may be viewed by guest users without joining.
|
||||
*/
|
||||
bool worldReadable() const;
|
||||
|
||||
/**
|
||||
* @brief Whether the local user is a member of the rooom.
|
||||
*/
|
||||
bool isJoined() const;
|
||||
|
||||
/**
|
||||
* @brief Whether the room is a space.
|
||||
*/
|
||||
bool isSpace() const;
|
||||
|
||||
private:
|
||||
QList<SpaceTreeItem *> m_children;
|
||||
SpaceTreeItem *m_parentItem;
|
||||
|
||||
QString m_id;
|
||||
QString m_name;
|
||||
QString m_canonicalAlias;
|
||||
QString m_topic;
|
||||
int m_memberCount;
|
||||
QUrl m_avatarUrl;
|
||||
bool m_allowGuests;
|
||||
bool m_worldReadable;
|
||||
bool m_isSpace;
|
||||
};
|
||||
@@ -156,9 +156,30 @@ void NeoChatConnection::deactivateAccount(const QString &password)
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatConnection::createRoom(const QString &name, const QString &topic)
|
||||
void NeoChatConnection::createRoom(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
|
||||
{
|
||||
const auto job = Connection::createRoom(Connection::PublishRoom, {}, name, topic, {});
|
||||
QVector<CreateRoomJob::StateEvent> initialStateEvents;
|
||||
if (!parent.isEmpty()) {
|
||||
initialStateEvents.append(CreateRoomJob::StateEvent{
|
||||
"m.space.parent"_ls,
|
||||
QJsonObject{
|
||||
{"canonical"_ls, true},
|
||||
{"via"_ls, QJsonArray{domain()}},
|
||||
},
|
||||
parent,
|
||||
});
|
||||
}
|
||||
|
||||
const auto job = Connection::createRoom(Connection::PublishRoom, QString(), name, topic, QStringList(), {}, {}, {}, initialStateEvents);
|
||||
if (!parent.isEmpty()) {
|
||||
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
|
||||
if (setChildParent) {
|
||||
if (auto parentRoom = room(parent)) {
|
||||
parentRoom->setState(QLatin1String("m.space.child"), job->roomId(), QJsonObject{{QLatin1String("via"), QJsonArray{domain()}}});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Room creation failed: %1", job->errorString()));
|
||||
});
|
||||
@@ -167,9 +188,30 @@ void NeoChatConnection::createRoom(const QString &name, const QString &topic)
|
||||
});
|
||||
}
|
||||
|
||||
void NeoChatConnection::createSpace(const QString &name, const QString &topic)
|
||||
void NeoChatConnection::createSpace(const QString &name, const QString &topic, const QString &parent, bool setChildParent)
|
||||
{
|
||||
const auto job = Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, {}, {}, QJsonObject{{"type"_ls, "m.space"_ls}});
|
||||
QVector<CreateRoomJob::StateEvent> initialStateEvents;
|
||||
if (!parent.isEmpty()) {
|
||||
initialStateEvents.append(CreateRoomJob::StateEvent{
|
||||
"m.space.parent"_ls,
|
||||
QJsonObject{
|
||||
{"canonical"_ls, true},
|
||||
{"via"_ls, QJsonArray{domain()}},
|
||||
},
|
||||
parent,
|
||||
});
|
||||
}
|
||||
|
||||
const auto job = Connection::createRoom(Connection::UnpublishRoom, {}, name, topic, {}, {}, {}, false, initialStateEvents, {}, QJsonObject{{"type"_ls, "m.space"_ls}});
|
||||
if (!parent.isEmpty()) {
|
||||
connect(job, &Quotient::CreateRoomJob::success, this, [this, parent, setChildParent, job]() {
|
||||
if (setChildParent) {
|
||||
if (auto parentRoom = room(parent)) {
|
||||
parentRoom->setState(QLatin1String("m.space.child"), job->roomId(), QJsonObject{{QLatin1String("via"), QJsonArray{domain()}}});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
connect(job, &CreateRoomJob::failure, this, [this, job] {
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Space creation failed: %1", job->errorString()));
|
||||
});
|
||||
|
||||
@@ -54,12 +54,12 @@ public:
|
||||
/**
|
||||
* @brief Create new room for a group chat.
|
||||
*/
|
||||
Q_INVOKABLE void createRoom(const QString &name, const QString &topic);
|
||||
Q_INVOKABLE void createRoom(const QString &name, const QString &topic, const QString &parent = {}, bool setChildParent = false);
|
||||
|
||||
/**
|
||||
* @brief Create new space.
|
||||
*/
|
||||
Q_INVOKABLE void createSpace(const QString &name, const QString &topic);
|
||||
Q_INVOKABLE void createSpace(const QString &name, const QString &topic, const QString &parent = {}, bool setChildParent = false);
|
||||
|
||||
Q_SIGNALS:
|
||||
void labelChanged();
|
||||
|
||||
@@ -1109,6 +1109,25 @@ bool NeoChatRoom::isSpace()
|
||||
return creationEvent->roomType() == RoomType::Space;
|
||||
}
|
||||
|
||||
void NeoChatRoom::addChild(const QString &childId, bool setChildParent)
|
||||
{
|
||||
if (!isSpace()) {
|
||||
return;
|
||||
}
|
||||
if (!canSendEvent("m.space.child"_ls)) {
|
||||
return;
|
||||
}
|
||||
setState("m.space.child"_ls, childId, QJsonObject{{QLatin1String("via"), QJsonArray{connection()->domain()}}});
|
||||
|
||||
if (setChildParent) {
|
||||
if (auto child = static_cast<NeoChatRoom *>(connection()->room(childId))) {
|
||||
if (child->canSendState("m.space.parent"_ls)) {
|
||||
child->setState("m.space.parent"_ls, id(), QJsonObject{{"canonical"_ls, true}, {"via"_ls, QJsonArray{connection()->domain()}}});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PushNotificationState::State NeoChatRoom::pushNotificationState() const
|
||||
{
|
||||
return m_currentPushNotificationState;
|
||||
|
||||
@@ -589,6 +589,8 @@ public:
|
||||
|
||||
[[nodiscard]] bool isSpace();
|
||||
|
||||
Q_INVOKABLE void addChild(const QString &childId, bool setChildParent = false);
|
||||
|
||||
bool isInvite() const;
|
||||
|
||||
Q_INVOKABLE void clearInvitationNotification();
|
||||
|
||||
@@ -2,44 +2,225 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-or-later OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
import org.kde.kirigamiaddons.labs.components as Components
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
title: i18nc("@title", "Create a Room")
|
||||
property string parentId: ""
|
||||
|
||||
property bool isSpace: false
|
||||
|
||||
property bool showChildType: false
|
||||
|
||||
property bool showCreateChoice: false
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
signal addChild(string childId, bool setChildParent)
|
||||
signal newChild(string childName)
|
||||
|
||||
title: isSpace ? i18nc("@title", "Create a Space") : i18nc("@title", "Create a Room")
|
||||
|
||||
Component.onCompleted: roomNameField.forceActiveFocus()
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Room Information")
|
||||
title: root.isSpace ? i18n("New Space Information") : i18n("New Room Information")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormComboBoxDelegate {
|
||||
id: roomTypeCombo
|
||||
property bool isInitialising: true
|
||||
|
||||
visible: root.showChildType
|
||||
|
||||
text: i18n("Select type")
|
||||
model: ListModel {
|
||||
id: roomTypeModel
|
||||
}
|
||||
textRole: "text"
|
||||
valueRole: "isSpace"
|
||||
|
||||
Component.onCompleted: {
|
||||
currentIndex = indexOfValue(root.isSpace)
|
||||
roomTypeModel.append({"text": i18n("Room"), "isSpace": false});
|
||||
roomTypeModel.append({"text": i18n("Space"), "isSpace": true});
|
||||
roomTypeCombo.currentIndex = 0
|
||||
roomTypeCombo.isInitialising = false
|
||||
}
|
||||
onCurrentValueChanged: {
|
||||
if (!isInitialising) {
|
||||
root.isSpace = currentValue
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: roomNameField
|
||||
label: i18n("Room name:")
|
||||
label: i18n("Name:")
|
||||
onAccepted: if (roomNameField.text.length > 0) roomTopicField.forceActiveFocus();
|
||||
}
|
||||
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: roomTopicField
|
||||
label: i18n("Room topic:")
|
||||
label: i18n("Topic:")
|
||||
onAccepted: ok.clicked()
|
||||
}
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
id: newOfficialCheck
|
||||
visible: root.parentId.length > 0
|
||||
text: i18n("Make this parent official")
|
||||
checked: true
|
||||
}
|
||||
FormCard.FormButtonDelegate {
|
||||
id: ok
|
||||
text: i18nc("@action:button", "Ok")
|
||||
enabled: roomNameField.text.length > 0
|
||||
onClicked: {
|
||||
root.connection.createRoom(roomNameField.text, roomTopicField.text);
|
||||
if (root.isSpace) {
|
||||
root.connection.createSpace(roomNameField.text, roomTopicField.text, root.parentId, newOfficialCheck.checked);
|
||||
} else {
|
||||
root.connection.createRoom(roomNameField.text, roomTopicField.text, root.parentId, newOfficialCheck.checked);
|
||||
}
|
||||
root.newChild(roomNameField.text)
|
||||
root.closeDialog()
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
visible: root.showChildType
|
||||
title: i18n("Select Exisiting Room")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
visible: root.showChildType
|
||||
FormCard.FormButtonDelegate {
|
||||
visible: !chosenRoomDelegate.visible
|
||||
text: i18nc("@action:button", "Pick room")
|
||||
onClicked: {
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
chosenRoomDelegate.roomId = roomId;
|
||||
chosenRoomDelegate.displayName = displayName;
|
||||
chosenRoomDelegate.avatarUrl = avatarUrl;
|
||||
chosenRoomDelegate.alias = alias;
|
||||
chosenRoomDelegate.topic = topic;
|
||||
chosenRoomDelegate.memberCount = memberCount;
|
||||
chosenRoomDelegate.isJoined = isJoined;
|
||||
chosenRoomDelegate.visible = true;
|
||||
})
|
||||
}
|
||||
}
|
||||
FormCard.AbstractFormDelegate {
|
||||
id: chosenRoomDelegate
|
||||
property string roomId
|
||||
property string displayName
|
||||
property url avatarUrl
|
||||
property string alias
|
||||
property string topic
|
||||
property int memberCount
|
||||
property bool isJoined
|
||||
|
||||
visible: false
|
||||
|
||||
contentItem: RowLayout {
|
||||
Components.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 2
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
|
||||
source: chosenRoomDelegate.avatarUrl
|
||||
name: chosenRoomDelegate.displayName
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
level: 4
|
||||
text: chosenRoomDelegate.displayName
|
||||
font.bold: true
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
QQC2.Label {
|
||||
visible: chosenRoomDelegate.isJoined
|
||||
text: i18n("Joined")
|
||||
color: Kirigami.Theme.linkColor
|
||||
}
|
||||
}
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
visible: text
|
||||
text: chosenRoomDelegate.topic ? chosenRoomDelegate.topic.replace(/(\r\n\t|\n|\r\t)/gm," ") : ""
|
||||
textFormat: Text.PlainText
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.NoWrap
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Kirigami.Icon {
|
||||
source: "user"
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
implicitHeight: Kirigami.Units.iconSizes.small
|
||||
implicitWidth: Kirigami.Units.iconSizes.small
|
||||
}
|
||||
QQC2.Label {
|
||||
text: chosenRoomDelegate.memberCount + " " + (chosenRoomDelegate.alias ?? chosenRoomDelegate.roomId)
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
elide: Text.ElideRight
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
chosenRoomDelegate.roomId = roomId;
|
||||
chosenRoomDelegate.displayName = displayName;
|
||||
chosenRoomDelegate.avatarUrl = avatarUrl;
|
||||
chosenRoomDelegate.alias = alias;
|
||||
chosenRoomDelegate.topic = topic;
|
||||
chosenRoomDelegate.memberCount = memberCount;
|
||||
chosenRoomDelegate.isJoined = isJoined;
|
||||
chosenRoomDelegate.visible = true;
|
||||
})
|
||||
}
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
id: exisitingOfficialCheck
|
||||
visible: root.parentId.length > 0
|
||||
text: i18n("Make this parent official")
|
||||
description: enabled ? i18n("You have the required privilege level in the child to set this state") : i18n("You do not have a high enough privilege level in the child to set this state")
|
||||
checked: enabled
|
||||
|
||||
enabled: {
|
||||
if (chosenRoomDelegate.visible) {
|
||||
let room = root.connection.room(chosenRoomDelegate.roomId);
|
||||
if (room) {
|
||||
if (room.canSendState("m.space.parent")) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Ok")
|
||||
enabled: chosenRoomDelegate.visible
|
||||
onClicked: {
|
||||
root.addChild(chosenRoomDelegate.roomId, exisitingOfficialCheck.checked);
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
|
||||
FormCard.FormCardPage {
|
||||
id: root
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
title: i18n("Create a Space")
|
||||
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Create a Space")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: nameDelegate
|
||||
label: i18n("Space name")
|
||||
}
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: topicDelegate
|
||||
label: i18n("Space topic (optional)")
|
||||
}
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18n("Create space")
|
||||
onClicked: {
|
||||
root.connection.createSpace(nameDelegate.text, topicDelegate.text)
|
||||
root.close()
|
||||
root.destroy()
|
||||
}
|
||||
enabled: nameDelegate.text.length > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,7 +48,7 @@ RowLayout {
|
||||
text: i18n("Create a Space")
|
||||
icon.name: "list-add"
|
||||
onTriggered: {
|
||||
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateSpaceDialog.qml", {connection: root.connection}, {title: i18nc("@title", "Create a Space")})
|
||||
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {connection: root.connection, isSpace: true, title: i18nc("@title", "Create a Space")}, {title: i18nc("@title", "Create a Space")})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,10 @@ import org.kde.neochat
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
/**
|
||||
* @brief The current room that user is viewing.
|
||||
*/
|
||||
required property NeoChatRoom room
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -26,8 +30,8 @@ ColumnLayout {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.large
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.large
|
||||
|
||||
name: room ? room.displayName : ""
|
||||
source: room && room.avatarMediaId ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||
name: root.room ? root.room.displayName : ""
|
||||
source: root.room && root.room.avatarMediaId ? ("image://mxc/" + root.room.avatarMediaId) : ""
|
||||
|
||||
Rectangle {
|
||||
visible: room.usesEncryption
|
||||
@@ -58,7 +62,7 @@ ColumnLayout {
|
||||
|
||||
Kirigami.Heading {
|
||||
Layout.fillWidth: true
|
||||
text: room ? room.displayName : i18n("No name")
|
||||
text: root.room ? root.room.displayName : i18n("No name")
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
@@ -67,8 +71,8 @@ ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: TextEdit.PlainText
|
||||
visible: room && room.canonicalAlias
|
||||
text: room && room.canonicalAlias ? room.canonicalAlias : ""
|
||||
visible: root.room && root.room.canonicalAlias
|
||||
text: root.room && root.room.canonicalAlias ? root.room.canonicalAlias : ""
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -78,7 +82,7 @@ ColumnLayout {
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
|
||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||
text: root.room && root.room.topic ? root.room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
||||
textFormat: TextEdit.MarkdownText
|
||||
wrapMode: Text.Wrap
|
||||
|
||||
@@ -136,6 +136,7 @@ Kirigami.OverlayDrawer {
|
||||
Kirigami.NavigationTabBar {
|
||||
id: navigationBar
|
||||
Layout.fillWidth: true
|
||||
visible: !root.room.isSpace
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
|
||||
@@ -73,6 +73,7 @@ Kirigami.Page {
|
||||
|
||||
footer: Kirigami.NavigationTabBar {
|
||||
id: navigationBar
|
||||
visible: !root.room.isSpace
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ QQC2.ScrollView {
|
||||
/**
|
||||
* @brief The title that should be displayed for this component if available.
|
||||
*/
|
||||
readonly property string title: i18nc("@action:title", "Room information")
|
||||
readonly property string title: root.room.isSpace ? i18nc("@action:title", "Space Members") : i18nc("@action:title", "Room information")
|
||||
|
||||
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||
@@ -57,6 +57,7 @@ QQC2.ScrollView {
|
||||
active: true
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
visible: !root.room.isSpace
|
||||
sourceComponent: root.room.isDirectChat() ? directChatDrawerHeader : groupChatDrawerHeader
|
||||
onItemChanged: if (item) {
|
||||
userList.positionViewAtBeginning();
|
||||
@@ -64,6 +65,7 @@ QQC2.ScrollView {
|
||||
}
|
||||
|
||||
Kirigami.ListSectionHeader {
|
||||
visible: !root.room.isSpace
|
||||
label: i18n("Options")
|
||||
activeFocusOnTab: false
|
||||
|
||||
@@ -75,7 +77,7 @@ QQC2.ScrollView {
|
||||
|
||||
icon.name: "tools"
|
||||
text: i18n("Open developer tools")
|
||||
visible: Config.developerTools
|
||||
visible: Config.developerTools && !root.room.isSpace
|
||||
|
||||
Layout.fillWidth: true
|
||||
|
||||
@@ -86,7 +88,7 @@ QQC2.ScrollView {
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: searchButton
|
||||
|
||||
visible: !root.room.isSpace
|
||||
icon.name: "search"
|
||||
text: i18n("Search in this room")
|
||||
|
||||
@@ -104,7 +106,7 @@ QQC2.ScrollView {
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: favouriteButton
|
||||
|
||||
visible: !root.room.isSpace
|
||||
icon.name: root.room && root.room.isFavourite ? "rating" : "rating-unrated"
|
||||
text: root.room && root.room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||
|
||||
@@ -115,7 +117,7 @@ QQC2.ScrollView {
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: locationsButton
|
||||
|
||||
visible: !root.room.isSpace
|
||||
icon.name: "map-flat"
|
||||
text: i18n("Show locations for this room")
|
||||
|
||||
@@ -240,7 +242,9 @@ QQC2.ScrollView {
|
||||
|
||||
Component {
|
||||
id: groupChatDrawerHeader
|
||||
GroupChatDrawerHeader {}
|
||||
GroupChatDrawerHeader {
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
|
||||
131
src/qml/SpaceHierarchyDelegate.qml
Normal file
131
src/qml/SpaceHierarchyDelegate.qml
Normal file
@@ -0,0 +1,131 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
import org.kde.kirigamiaddons.labs.components as Components
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Item {
|
||||
id: root
|
||||
required property TreeView treeView
|
||||
required property bool isTreeNode
|
||||
required property bool expanded
|
||||
required property int hasChildren
|
||||
required property int depth
|
||||
required property string roomId
|
||||
required property string displayName
|
||||
required property url avatarUrl
|
||||
required property bool isSpace
|
||||
required property int memberCount
|
||||
required property string topic
|
||||
required property bool isJoined
|
||||
required property bool canAddChildren
|
||||
|
||||
signal createRoom()
|
||||
signal enterRoom()
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
anchors.centerIn: root
|
||||
width: sizeHelper.currentWidth
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
RowLayout {
|
||||
spacing: 0
|
||||
Item {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium * (root.depth + (root.isSpace ? 0 : 1))
|
||||
}
|
||||
Kirigami.Icon {
|
||||
visible: root.isSpace
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
source: root.hasChildren ? (root.expanded ? "go-up" : "go-down") : "go-next"
|
||||
}
|
||||
}
|
||||
Components.Avatar {
|
||||
Layout.fillHeight: true
|
||||
Layout.preferredWidth: height
|
||||
implicitWidth: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||
source: root.avatarUrl
|
||||
name: root.displayName
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignBottom
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
QQC2.Label {
|
||||
id: label
|
||||
text: root.displayName
|
||||
elide: Text.ElideRight
|
||||
textFormat: Text.PlainText
|
||||
}
|
||||
QQC2.Label {
|
||||
visible: root.isJoined
|
||||
text: i18n("Joined")
|
||||
color: Kirigami.Theme.linkColor
|
||||
}
|
||||
}
|
||||
QQC2.Label {
|
||||
id: subtitle
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
|
||||
text: root.memberCount + (root.topic !== "" ? i18nc("number of room members", " members - ") + root.topic : i18nc("number of room members", " members"))
|
||||
elide: Text.ElideRight
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: Text.PlainText
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
visible: root.isSpace && root.canAddChildren
|
||||
text: i18nc("@button", "Add new child")
|
||||
icon.name: "list-add"
|
||||
onClicked: root.createRoom()
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
|
||||
TapHandler {
|
||||
onTapped: {
|
||||
if (root.isSpace) {
|
||||
root.treeView.toggleExpanded(row)
|
||||
} else {
|
||||
if (root.isJoined) {
|
||||
root.enterRoom()
|
||||
} else {
|
||||
Controller.joinRoom(root.roomId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DelegateSizeHelper {
|
||||
id: sizeHelper
|
||||
startBreakpoint: Kirigami.Units.gridUnit * 46
|
||||
endBreakpoint: Kirigami.Units.gridUnit * 66
|
||||
startPercentWidth: 100
|
||||
endPercentWidth: 85
|
||||
maxWidth: Kirigami.Units.gridUnit * 60
|
||||
|
||||
parentWidth: root.treeView ? root.treeView.width : 0
|
||||
}
|
||||
}
|
||||
178
src/qml/SpaceHomePage.qml
Normal file
178
src/qml/SpaceHomePage.qml
Normal file
@@ -0,0 +1,178 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
readonly property NeoChatRoom currentRoom: RoomManager.currentRoom
|
||||
|
||||
padding: 0
|
||||
|
||||
ColumnLayout {
|
||||
id: columnLayout
|
||||
anchors.fill: parent
|
||||
spacing: 0
|
||||
|
||||
Item {
|
||||
id: headerItem
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||
implicitHeight: headerColumn.implicitHeight
|
||||
|
||||
ColumnLayout {
|
||||
id: headerColumn
|
||||
anchors.centerIn: headerItem
|
||||
width: sizeHelper.currentWidth
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
GroupChatDrawerHeader {
|
||||
id: header
|
||||
Layout.fillWidth: true
|
||||
room: root.currentRoom
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
QQC2.Button {
|
||||
visible: root.currentRoom.canSendState("invite")
|
||||
text: i18nc("@button", "Invite user to space")
|
||||
icon.name: "list-add-user"
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/InviteUserPage.qml", {room: root.currentRoom}, {title: i18nc("@title", "Invite a User")})
|
||||
}
|
||||
QQC2.Button {
|
||||
visible: root.currentRoom.canSendState("m.space.child")
|
||||
text: i18nc("@button", "Add new child")
|
||||
icon.name: "list-add"
|
||||
onClicked: _private.createRoom(root.currentRoom.id)
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@button", "Leave the space")
|
||||
icon.name: "go-previous"
|
||||
onClicked: RoomManager.leaveRoom(root.currentRoom)
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Button {
|
||||
text: i18nc("@button", "Space settings")
|
||||
icon.name: "settings-configure"
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
onClicked: applicationWindow().pageStack.pushDialogLayer('qrc:/org/kde/neochat/qml/Categories.qml', {room: root.currentRoom, connection: root.currentRoom.connection}, { title: i18n("Room Settings") })
|
||||
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
}
|
||||
}
|
||||
Kirigami.SearchField {
|
||||
Layout.fillWidth: true
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||
onTextChanged: spaceChildSortFilterModel.filterText = text
|
||||
}
|
||||
}
|
||||
DelegateSizeHelper {
|
||||
id: sizeHelper
|
||||
startBreakpoint: Kirigami.Units.gridUnit * 46
|
||||
endBreakpoint: Kirigami.Units.gridUnit * 66
|
||||
startPercentWidth: 100
|
||||
endPercentWidth: 85
|
||||
maxWidth: Kirigami.Units.gridUnit * 60
|
||||
|
||||
parentWidth: columnLayout.width
|
||||
}
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.ScrollView {
|
||||
id: hierarchyScrollView
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: !spaceChildrenModel.loading
|
||||
|
||||
TreeView {
|
||||
id: spaceTree
|
||||
columnWidthProvider: function (column) { return spaceTree.width }
|
||||
|
||||
clip: true
|
||||
|
||||
model: SpaceChildSortFilterModel {
|
||||
id: spaceChildSortFilterModel
|
||||
sourceModel: SpaceChildrenModel {
|
||||
id: spaceChildrenModel
|
||||
space: root.currentRoom
|
||||
}
|
||||
}
|
||||
|
||||
delegate: SpaceHierarchyDelegate {
|
||||
onCreateRoom: _private.createRoom(roomId)
|
||||
onEnterRoom: _private.enterRoom(roomId)
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: spaceChildrenModel.loading
|
||||
|
||||
Loader {
|
||||
active: spaceChildrenModel.loading
|
||||
anchors.centerIn: parent
|
||||
sourceComponent: Kirigami.LoadingPlaceholder {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
Kirigami.Theme.inherit: false
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _private
|
||||
function createRoom(parentId) {
|
||||
let dialog = applicationWindow().pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/CreateRoomDialog.qml", {
|
||||
title: i18nc("@title", "Create a Child"),
|
||||
connection: root.currentRoom.connection,
|
||||
parentId : parentId,
|
||||
showChildType: true,
|
||||
showCreateChoice: true
|
||||
}, {
|
||||
title: i18nc("@title", "Create a Child")
|
||||
})
|
||||
dialog.addChild.connect((childId, setChildParent) => {
|
||||
// We have to get a room object from the connection as we may not
|
||||
// be adding to the top level parent.
|
||||
let parent = root.currentRoom.connection.room(parentId)
|
||||
if (parent) {
|
||||
parent.addChild(childId, setChildParent)
|
||||
}
|
||||
})
|
||||
dialog.newChild.connect(childName => {spaceChildrenModel.addPendingChild(childName)})
|
||||
}
|
||||
|
||||
function enterRoom(roomId) {
|
||||
let room = root.currentRoom.connection.room(roomId)
|
||||
if (room) {
|
||||
RoomManager.enterRoom(room)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ Loader {
|
||||
QQC2.MenuItem {
|
||||
text: i18nc("'Space' is a matrix space", "View Space")
|
||||
icon.name: "view-list-details"
|
||||
onTriggered: RoomManager.enterRoom(room);
|
||||
onTriggered: RoomManager.enterSpaceHome(room);
|
||||
}
|
||||
|
||||
QQC2.MenuItem {
|
||||
|
||||
@@ -21,6 +21,7 @@ Kirigami.ApplicationWindow {
|
||||
property bool roomListLoaded: false
|
||||
|
||||
property RoomPage roomPage
|
||||
property SpaceHomePage spaceHomePage
|
||||
|
||||
property NeoChatConnection connection: Controller.activeConnection
|
||||
|
||||
@@ -96,15 +97,36 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
function onPushSpaceHome(room) {
|
||||
root.spaceHomePage = pageStack.push("qrc:/org/kde/neochat/qml/SpaceHomePage.qml");
|
||||
root.spaceHomePage.forceActiveFocus();
|
||||
}
|
||||
|
||||
function onReplaceRoom(room, event) {
|
||||
const roomItem = pageStack.get(pageStack.depth - 1);
|
||||
pageStack.currentIndex = pageStack.depth - 1;
|
||||
if (root.roomPage) {
|
||||
pageStack.currentIndex = pageStack.depth - 1;
|
||||
} else {
|
||||
pageStack.pop();
|
||||
root.roomPage = pageStack.push("qrc:/org/kde/neochat/qml/RoomPage.qml", {connection: root.connection});
|
||||
root.spaceHomePage = null;
|
||||
}
|
||||
root.roomPage.forceActiveFocus();
|
||||
if (event.length > 0) {
|
||||
roomItem.goToEvent(event);
|
||||
root.roomPage.goToEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
function onReplaceSpaceHome(room) {
|
||||
if (root.spaceHomePage) {
|
||||
pageStack.currentIndex = pageStack.depth - 1;
|
||||
} else {
|
||||
pageStack.pop();
|
||||
root.spaceHomePage = pageStack.push("qrc:/org/kde/neochat/qml/SpaceHomePage.qml");
|
||||
root.roomPage = null;
|
||||
}
|
||||
root.spaceHomePage.forceActiveFocus();
|
||||
}
|
||||
|
||||
function goToEvent(event) {
|
||||
if (event.length > 0) {
|
||||
roomItem.goToEvent(event);
|
||||
@@ -335,13 +357,6 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: createSpaceDialog
|
||||
CreateSpaceDialog {
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: roomWindow
|
||||
RoomWindow {}
|
||||
|
||||
@@ -179,7 +179,11 @@ void RoomManager::openRoomForActiveConnection()
|
||||
const auto room = qobject_cast<NeoChatRoom *>(Controller::instance().activeConnection()->room(roomId));
|
||||
|
||||
if (room) {
|
||||
enterRoom(room);
|
||||
if (room->isSpace()) {
|
||||
enterSpaceHome(room);
|
||||
} else {
|
||||
enterRoom(room);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -222,6 +226,34 @@ void RoomManager::openWindow(NeoChatRoom *room)
|
||||
Q_EMIT openRoomInNewWindow(room);
|
||||
}
|
||||
|
||||
void RoomManager::enterSpaceHome(NeoChatRoom *spaceRoom)
|
||||
{
|
||||
if (!spaceRoom->isSpace()) {
|
||||
return;
|
||||
}
|
||||
// If replacing a normal room message timeline make sure any edit is cancelled.
|
||||
if (m_currentRoom && !m_currentRoom->chatBoxEditId().isEmpty()) {
|
||||
m_currentRoom->setChatBoxEditId({});
|
||||
}
|
||||
// Save the chatbar text for the current room if any before switching
|
||||
if (m_currentRoom && m_chatDocumentHandler) {
|
||||
if (m_chatDocumentHandler->document()) {
|
||||
m_currentRoom->setSavedText(m_chatDocumentHandler->document()->textDocument()->toPlainText());
|
||||
}
|
||||
}
|
||||
m_lastCurrentRoom = std::exchange(m_currentRoom, spaceRoom);
|
||||
Q_EMIT currentRoomChanged();
|
||||
|
||||
if (!m_lastCurrentRoom) {
|
||||
Q_EMIT pushSpaceHome(spaceRoom);
|
||||
} else {
|
||||
Q_EMIT replaceSpaceHome(m_currentRoom);
|
||||
}
|
||||
|
||||
// Save last open room
|
||||
m_lastRoomConfig.writeEntry(Controller::instance().activeConnection()->userId(), spaceRoom->id());
|
||||
}
|
||||
|
||||
UriResolveResult RoomManager::visitUser(User *user, const QString &action)
|
||||
{
|
||||
if (action == "mention"_ls || action.isEmpty()) {
|
||||
|
||||
@@ -127,6 +127,13 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void leaveRoom(NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Enter the home page of the given space.
|
||||
*
|
||||
* This method will tell NeoChat to open the home page for the given space.
|
||||
*/
|
||||
Q_INVOKABLE void enterSpaceHome(NeoChatRoom *spaceRoom);
|
||||
|
||||
// Overrided methods from UriResolverBase
|
||||
/**
|
||||
* @brief Resolve a user URI.
|
||||
@@ -263,6 +270,26 @@ Q_SIGNALS:
|
||||
*/
|
||||
void replaceRoom(NeoChatRoom *room, const QString &event);
|
||||
|
||||
/**
|
||||
* @brief Push a new space home page.
|
||||
*
|
||||
* Signal triggered when the main window pageStack should push a new page with
|
||||
* the space home for the given space room.
|
||||
*
|
||||
* @param spaceRoom the space room to be shown on the new page.
|
||||
*/
|
||||
void pushSpaceHome(NeoChatRoom *spaceRoom);
|
||||
|
||||
/**
|
||||
* @brief Replace the existing space home.
|
||||
*
|
||||
* Signal triggered when the currently displayed room page should be changed
|
||||
* to the space home for the given space room.
|
||||
*
|
||||
* @param spaceRoom the space room to be shown on the new page.
|
||||
*/
|
||||
void replaceSpaceHome(NeoChatRoom *spaceRoom);
|
||||
|
||||
/**
|
||||
* @brief Go to the specified event in the current room.
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user