Move remaining code to app module
There's still some stuff that could potentially go elsewhere but I think it's enough for now.
This commit is contained in:
79
src/app/models/commonroomsmodel.cpp
Normal file
79
src/app/models/commonroomsmodel.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "commonroomsmodel.h"
|
||||
#include "jobs/neochatgetcommonroomsjob.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
CommonRoomsModel::CommonRoomsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
NeoChatConnection *CommonRoomsModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void CommonRoomsModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
reload();
|
||||
}
|
||||
|
||||
QString CommonRoomsModel::userId() const
|
||||
{
|
||||
return m_userId;
|
||||
}
|
||||
|
||||
void CommonRoomsModel::setUserId(const QString &userId)
|
||||
{
|
||||
m_userId = userId;
|
||||
Q_EMIT userIdChanged();
|
||||
reload();
|
||||
}
|
||||
|
||||
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
|
||||
{
|
||||
Q_UNUSED(index)
|
||||
Q_UNUSED(roleName)
|
||||
return {};
|
||||
}
|
||||
|
||||
int CommonRoomsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_commonRooms.size();
|
||||
}
|
||||
|
||||
void CommonRoomsModel::reload()
|
||||
{
|
||||
if (!m_connection || m_userId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_connection->canCheckMutualRooms()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Checking if you have mutual rooms with yourself doesn't make sense and servers reject it too
|
||||
if (m_connection->userId() == m_userId) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_connection->callApi<NeochatGetCommonRoomsJob>(m_userId).then([this](const auto job) {
|
||||
const auto &replyData = job->jsonData();
|
||||
beginResetModel();
|
||||
for (const auto &roomId : replyData[u"joined"_s].toArray()) {
|
||||
m_commonRooms.push_back(roomId.toString());
|
||||
}
|
||||
endResetModel();
|
||||
Q_EMIT countChanged();
|
||||
});
|
||||
}
|
||||
|
||||
#include "moc_commonroomsmodel.cpp"
|
||||
58
src/app/models/commonroomsmodel.h
Normal file
58
src/app/models/commonroomsmodel.h
Normal file
@@ -0,0 +1,58 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
/**
|
||||
* @brief Model to show the common or mutual rooms between you and another user.
|
||||
*/
|
||||
class CommonRoomsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
Q_PROPERTY(NeoChatConnection *connection WRITE setConnection READ connection NOTIFY connectionChanged REQUIRED)
|
||||
Q_PROPERTY(QString userId WRITE setUserId READ userId NOTIFY userIdChanged REQUIRED)
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
TextRole = Qt::DisplayRole,
|
||||
LongitudeRole,
|
||||
LatitudeRole,
|
||||
AssetRole,
|
||||
AuthorRole,
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit CommonRoomsModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] NeoChatConnection *connection() const;
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
|
||||
[[nodiscard]] QString userId() const;
|
||||
void setUserId(const QString &userId);
|
||||
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
|
||||
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void userIdChanged();
|
||||
void countChanged();
|
||||
|
||||
private:
|
||||
void reload();
|
||||
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
QString m_userId;
|
||||
QList<QString> m_commonRooms;
|
||||
};
|
||||
164
src/app/models/notificationsmodel.cpp
Normal file
164
src/app/models/notificationsmodel.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "notificationsmodel.h"
|
||||
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/uri.h>
|
||||
|
||||
#include "eventhandler.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NotificationsModel::NotificationsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int NotificationsModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return m_notifications.count();
|
||||
}
|
||||
|
||||
QVariant NotificationsModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
auto row = index.row();
|
||||
if (row < 0 || row >= m_notifications.count()) {
|
||||
return {};
|
||||
}
|
||||
if (role == TextRole) {
|
||||
return m_notifications[row].text;
|
||||
}
|
||||
if (role == RoomIdRole) {
|
||||
return m_notifications[row].roomId;
|
||||
}
|
||||
if (role == AuthorName) {
|
||||
return m_notifications[row].authorName;
|
||||
}
|
||||
if (role == AuthorAvatar) {
|
||||
return m_notifications[row].authorAvatar;
|
||||
}
|
||||
if (role == RoomRole) {
|
||||
return QVariant::fromValue(m_connection->room(m_notifications[row].roomId));
|
||||
}
|
||||
if (role == EventIdRole) {
|
||||
return m_notifications[row].eventId;
|
||||
}
|
||||
if (role == RoomDisplayNameRole) {
|
||||
return m_notifications[row].roomDisplayName;
|
||||
}
|
||||
if (role == UriRole) {
|
||||
return Uri(m_notifications[row].roomId.toLatin1(), m_notifications[row].eventId.toLatin1()).toUrl();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> NotificationsModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{TextRole, "text"},
|
||||
{RoomIdRole, "roomId"},
|
||||
{AuthorName, "authorName"},
|
||||
{AuthorAvatar, "authorAvatar"},
|
||||
{RoomRole, "room"},
|
||||
{EventIdRole, "eventId"},
|
||||
{RoomDisplayNameRole, "roomDisplayName"},
|
||||
{UriRole, "uri"},
|
||||
};
|
||||
}
|
||||
|
||||
NeoChatConnection *NotificationsModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void NotificationsModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (m_connection) {
|
||||
// disconnect things...
|
||||
}
|
||||
if (!connection) {
|
||||
return;
|
||||
}
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
connect(connection, &Connection::syncDone, this, [this]() {
|
||||
loadData();
|
||||
});
|
||||
loadData();
|
||||
}
|
||||
|
||||
void NotificationsModel::loadData()
|
||||
{
|
||||
Q_ASSERT(m_connection);
|
||||
if (m_job || (m_notifications.size() && m_nextToken.isEmpty())) {
|
||||
return;
|
||||
}
|
||||
m_job = m_connection->callApi<GetNotificationsJob>(m_nextToken);
|
||||
Q_EMIT loadingChanged();
|
||||
connect(m_job, &BaseJob::finished, this, [this]() {
|
||||
m_nextToken = m_job->nextToken();
|
||||
Q_EMIT nextTokenChanged();
|
||||
for (const auto ¬ification : m_job->notifications()) {
|
||||
if (std::any_of(notification.actions.constBegin(), notification.actions.constEnd(), [](const QVariant &it) {
|
||||
if (it.canConvert<QVariantMap>()) {
|
||||
auto map = it.toMap();
|
||||
if (map["set_tweak"_L1] == "highlight"_L1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
})) {
|
||||
const auto &authorId = notification.event->fullJson()["sender"_L1].toString();
|
||||
const auto &room = m_connection->room(notification.roomId);
|
||||
if (!room) {
|
||||
continue;
|
||||
}
|
||||
auto u = room->member(authorId).avatarUrl();
|
||||
auto avatar = u.isEmpty() ? QUrl() : connection()->makeMediaUrl(u);
|
||||
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == u"mxc"_s ? avatar : QUrl();
|
||||
|
||||
const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get());
|
||||
beginInsertRows({}, m_notifications.length(), m_notifications.length());
|
||||
m_notifications += Notification{
|
||||
.roomId = notification.roomId,
|
||||
.text = room->member(authorId).htmlSafeDisplayName() + (roomEvent->is<StateEvent>() ? u" "_s : u": "_s)
|
||||
+ EventHandler::plainBody(dynamic_cast<NeoChatRoom *>(room), roomEvent, true),
|
||||
.authorName = room->member(authorId).htmlSafeDisplayName(),
|
||||
.authorAvatar = authorAvatar,
|
||||
.eventId = roomEvent->id(),
|
||||
.roomDisplayName = room->displayName(),
|
||||
};
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
m_job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
});
|
||||
}
|
||||
|
||||
bool NotificationsModel::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return !m_nextToken.isEmpty();
|
||||
}
|
||||
|
||||
void NotificationsModel::fetchMore(const QModelIndex &parent)
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
loadData();
|
||||
}
|
||||
|
||||
bool NotificationsModel::loading() const
|
||||
{
|
||||
return m_job;
|
||||
}
|
||||
|
||||
QString NotificationsModel::nextToken() const
|
||||
{
|
||||
return m_nextToken;
|
||||
}
|
||||
|
||||
#include "moc_notificationsmodel.cpp"
|
||||
68
src/app/models/notificationsmodel.h
Normal file
68
src/app/models/notificationsmodel.h
Normal file
@@ -0,0 +1,68 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QPointer>
|
||||
#include <QQmlEngine>
|
||||
#include <QVariant>
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
|
||||
class NotificationsModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||
Q_PROPERTY(QString nextToken READ nextToken NOTIFY nextTokenChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
TextRole = Qt::DisplayRole,
|
||||
RoomIdRole,
|
||||
AuthorName,
|
||||
AuthorAvatar,
|
||||
RoomRole,
|
||||
EventIdRole,
|
||||
RoomDisplayNameRole,
|
||||
UriRole,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
struct Notification {
|
||||
QString roomId;
|
||||
QString text;
|
||||
QString authorName;
|
||||
QUrl authorAvatar;
|
||||
QString eventId;
|
||||
QString roomDisplayName;
|
||||
};
|
||||
|
||||
NotificationsModel(QObject *parent = nullptr);
|
||||
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
|
||||
NeoChatConnection *connection() const;
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
bool loading() const;
|
||||
QString nextToken() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void loadingChanged();
|
||||
void nextTokenChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
void loadData();
|
||||
QList<Notification> m_notifications;
|
||||
QString m_nextToken;
|
||||
QPointer<Quotient::GetNotificationsJob> m_job;
|
||||
};
|
||||
182
src/app/models/serverlistmodel.cpp
Normal file
182
src/app/models/serverlistmodel.cpp
Normal file
@@ -0,0 +1,182 @@
|
||||
// SPDX-FileCopyrightText: 2022 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 "serverlistmodel.h"
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
#include <KSharedConfig>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
|
||||
using namespace Qt::StringLiterals;
|
||||
|
||||
ServerListModel::ServerListModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
QVariant ServerListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index.row() >= m_servers.count()) {
|
||||
qDebug() << "ServerListModel, something's wrong: index.row() >= m_notificationRules.count()";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == UrlRole) {
|
||||
return m_servers.at(index.row()).url;
|
||||
}
|
||||
|
||||
if (role == IsHomeServerRole) {
|
||||
return m_servers.at(index.row()).isHomeServer;
|
||||
}
|
||||
|
||||
if (role == IsAddServerDelegateRole) {
|
||||
return m_servers.at(index.row()).isAddServerDelegate;
|
||||
}
|
||||
|
||||
if (role == IsDeletableRole) {
|
||||
return m_servers.at(index.row()).isDeletable;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int ServerListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
return m_servers.count();
|
||||
}
|
||||
|
||||
void ServerListModel::checkServer(const QString &url)
|
||||
{
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
const KConfigGroup serverGroup = stateConfig->group(u"Servers"_s);
|
||||
|
||||
if (!serverGroup.hasKey(url)) {
|
||||
if (Quotient::isJobPending(m_checkServerJob)) {
|
||||
m_checkServerJob->abandon();
|
||||
}
|
||||
|
||||
m_checkServerJob = m_connection->callApi<Quotient::QueryPublicRoomsJob>(url, 1);
|
||||
connect(m_checkServerJob, &Quotient::BaseJob::success, this, [this, url] {
|
||||
Q_EMIT serverCheckComplete(url, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ServerListModel::addServer(const QString &url)
|
||||
{
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
KConfigGroup serverGroup = stateConfig->group(u"Servers"_s);
|
||||
|
||||
if (!serverGroup.hasKey(url)) {
|
||||
Server newServer = Server{
|
||||
url,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
};
|
||||
|
||||
beginInsertRows(QModelIndex(), m_servers.count() - 1, m_servers.count() - 1);
|
||||
m_servers.insert(rowCount() - 1, newServer);
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
serverGroup.writeEntry(url, url);
|
||||
stateConfig->sync();
|
||||
}
|
||||
|
||||
void ServerListModel::removeServerAtIndex(int row)
|
||||
{
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
KConfigGroup serverGroup = stateConfig->group(u"Servers"_s);
|
||||
|
||||
serverGroup.deleteEntry(data(index(row), UrlRole).toString());
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
m_servers.removeAt(row);
|
||||
endRemoveRows();
|
||||
|
||||
stateConfig->sync();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ServerListModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{UrlRole, QByteArrayLiteral("url")},
|
||||
{IsHomeServerRole, QByteArrayLiteral("isHomeServer")},
|
||||
{IsAddServerDelegateRole, QByteArrayLiteral("isAddServerDelegate")},
|
||||
{IsDeletableRole, QByteArrayLiteral("isDeletable")},
|
||||
};
|
||||
}
|
||||
|
||||
NeoChatConnection *ServerListModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void ServerListModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (m_connection == connection) {
|
||||
return;
|
||||
}
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
|
||||
initialize();
|
||||
}
|
||||
|
||||
void ServerListModel::initialize()
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
const KConfigGroup serverGroup = stateConfig->group(u"Servers"_s);
|
||||
|
||||
QString domain = m_connection->domain();
|
||||
|
||||
// Add the user's homeserver
|
||||
m_servers.append(Server{
|
||||
domain,
|
||||
true,
|
||||
false,
|
||||
false,
|
||||
});
|
||||
// Add matrix.org
|
||||
m_servers.append(Server{
|
||||
u"matrix.org"_s,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
});
|
||||
// Add each of the saved custom servers
|
||||
for (const auto &i : serverGroup.keyList()) {
|
||||
m_servers.append(Server{
|
||||
serverGroup.readEntry(i, QString()),
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
});
|
||||
}
|
||||
// Add add server delegate entry
|
||||
m_servers.append(Server{
|
||||
QString(),
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
});
|
||||
beginResetModel();
|
||||
}
|
||||
|
||||
#include "moc_serverlistmodel.cpp"
|
||||
116
src/app/models/serverlistmodel.h
Normal file
116
src/app/models/serverlistmodel.h
Normal file
@@ -0,0 +1,116 @@
|
||||
// SPDX-FileCopyrightText: 2022 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 <Quotient/csapi/list_public_rooms.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QPointer>
|
||||
#include <QQmlEngine>
|
||||
#include <QUrl>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
/**
|
||||
* @class ServerListModel
|
||||
*
|
||||
* This class defines the model for visualising a list of matrix servers.
|
||||
*
|
||||
* The list of servers is retrieved from the local cache. Any additions are also
|
||||
* stored locally so that they are retrieved on subsequent instantiations.
|
||||
*
|
||||
* The model also automatically adds the local user's home server and matrix.org to
|
||||
* the model. Finally the model also adds an entry to create a space in the model
|
||||
* for an "add new server" delegate.
|
||||
*/
|
||||
class ServerListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Define the data required to represent a server.
|
||||
*/
|
||||
struct Server {
|
||||
QString url; /**< Server URL. */
|
||||
bool isHomeServer; /**< Whether the server is the local user's home server. */
|
||||
bool isAddServerDelegate; /**< Wether the item is the "add new server" delegate. */
|
||||
bool isDeletable; /**< Whether the item can be deleted from the model. */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
UrlRole = Qt::UserRole + 1, /**< Server URL. */
|
||||
IsHomeServerRole, /**< Whether the server is the local user's home server. */
|
||||
IsAddServerDelegateRole, /**< Whether the item is the add new server delegate. */
|
||||
IsDeletableRole, /**< Whether the item can be deleted from the model. */
|
||||
};
|
||||
|
||||
explicit ServerListModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Start a check to see if the given URL is a valid matrix server.
|
||||
*
|
||||
* This function starts the check but due to the requests being asynchronous
|
||||
* the caller will need to watch the serverCheckComplete signal for confirmation.
|
||||
* The server URL should be treated as invalid until the signal is emitted true.
|
||||
*
|
||||
* @sa serverCheckComplete()
|
||||
*/
|
||||
Q_INVOKABLE void checkServer(const QString &url);
|
||||
|
||||
/**
|
||||
* @brief Add a new server to the model.
|
||||
*
|
||||
* The server will also be stored in local cache.
|
||||
*/
|
||||
Q_INVOKABLE void addServer(const QString &url);
|
||||
|
||||
/**
|
||||
* @brief Remove the server at the given index.
|
||||
*
|
||||
* The server will also be removed from local cache.
|
||||
*/
|
||||
Q_INVOKABLE void removeServerAtIndex(int index);
|
||||
|
||||
NeoChatConnection *connection() const;
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
|
||||
Q_SIGNALS:
|
||||
void serverCheckComplete(QString url, bool valid);
|
||||
void connectionChanged();
|
||||
|
||||
private:
|
||||
QList<Server> m_servers;
|
||||
QPointer<Quotient::QueryPublicRoomsJob> m_checkServerJob = nullptr;
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
|
||||
void initialize();
|
||||
};
|
||||
189
src/app/models/userdirectorylistmodel.cpp
Normal file
189
src/app/models/userdirectorylistmodel.cpp
Normal file
@@ -0,0 +1,189 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "userdirectorylistmodel.h"
|
||||
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
UserDirectoryListModel::UserDirectoryListModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
NeoChatConnection *UserDirectoryListModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void UserDirectoryListModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (m_connection == connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
|
||||
attempted = false;
|
||||
users.clear();
|
||||
|
||||
if (m_connection) {
|
||||
m_connection->disconnect(this);
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
|
||||
if (m_job) {
|
||||
m_job->abandon();
|
||||
m_job = nullptr;
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString UserDirectoryListModel::searchText() const
|
||||
{
|
||||
return m_searchText;
|
||||
}
|
||||
|
||||
void UserDirectoryListModel::setSearchText(const QString &value)
|
||||
{
|
||||
if (m_searchText == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_searchText = value;
|
||||
Q_EMIT searchTextChanged();
|
||||
|
||||
if (m_job) {
|
||||
m_job->abandon();
|
||||
m_job = nullptr;
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
|
||||
attempted = false;
|
||||
}
|
||||
|
||||
bool UserDirectoryListModel::searching() const
|
||||
{
|
||||
return m_job != nullptr;
|
||||
}
|
||||
|
||||
void UserDirectoryListModel::search(int limit)
|
||||
{
|
||||
if (limit < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_job) {
|
||||
qDebug() << "UserDirectoryListModel: Other jobs running, ignore";
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (attempted) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_job = m_connection->callApi<SearchUserDirectoryJob>(m_searchText, limit);
|
||||
Q_EMIT searchingChanged();
|
||||
|
||||
connect(m_job, &BaseJob::finished, this, [this] {
|
||||
attempted = true;
|
||||
|
||||
if (m_job->status() == BaseJob::Success) {
|
||||
auto users = m_job->results();
|
||||
|
||||
this->beginResetModel();
|
||||
this->users = users;
|
||||
this->endResetModel();
|
||||
}
|
||||
|
||||
this->m_job = nullptr;
|
||||
Q_EMIT searchingChanged();
|
||||
});
|
||||
}
|
||||
|
||||
QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (index.row() >= users.count()) {
|
||||
qDebug() << "UserDirectoryListModel, something's wrong: index.row() >= "
|
||||
"users.count()";
|
||||
return {};
|
||||
}
|
||||
auto user = users.at(index.row());
|
||||
if (role == DisplayNameRole) {
|
||||
auto displayName = user.displayName;
|
||||
if (!displayName.isEmpty()) {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
displayName = user.userId;
|
||||
if (!displayName.isEmpty()) {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
return u"Unknown User"_s;
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
auto avatarUrl = user.avatarUrl;
|
||||
if (avatarUrl.isEmpty() || !m_connection) {
|
||||
return QUrl();
|
||||
}
|
||||
return m_connection->makeMediaUrl(avatarUrl);
|
||||
}
|
||||
if (role == UserIDRole) {
|
||||
return user.userId;
|
||||
}
|
||||
if (role == DirectChatExistsRole) {
|
||||
if (!m_connection) {
|
||||
return false;
|
||||
};
|
||||
|
||||
auto userObj = m_connection->user(user.userId);
|
||||
auto directChats = m_connection->directChats();
|
||||
|
||||
if (userObj && directChats.contains(userObj)) {
|
||||
auto directChatsForUser = directChats.values(userObj);
|
||||
if (!directChatsForUser.isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> UserDirectoryListModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
|
||||
roles[DisplayNameRole] = "displayName";
|
||||
roles[AvatarRole] = "avatarUrl";
|
||||
roles[UserIDRole] = "userId";
|
||||
roles[DirectChatExistsRole] = "directChatExists";
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
int UserDirectoryListModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return users.count();
|
||||
}
|
||||
|
||||
#include "moc_userdirectorylistmodel.cpp"
|
||||
106
src/app/models/userdirectorylistmodel.h
Normal file
106
src/app/models/userdirectorylistmodel.h
Normal file
@@ -0,0 +1,106 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <Quotient/csapi/users.h>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
/**
|
||||
* @class UserDirectoryListModel
|
||||
*
|
||||
* This class defines the model for visualising the results of a user search.
|
||||
*
|
||||
* The model searches for users that have the given keyword in the matrix
|
||||
* ID or the display name. See
|
||||
* https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3user_directorysearch
|
||||
* for more info on matrix user searches.
|
||||
*/
|
||||
class UserDirectoryListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief The current connection that the model is getting users from.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The text to search the public room list for.
|
||||
*/
|
||||
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the model is searching.
|
||||
*/
|
||||
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
DisplayNameRole = Qt::DisplayRole, /**< The user's display name. */
|
||||
AvatarRole, /**< The source URL for the user's avatar. */
|
||||
UserIDRole, /**< Matrix ID of the user. */
|
||||
DirectChatExistsRole, /**< Whether there is already a direct chat with the user. */
|
||||
};
|
||||
|
||||
explicit UserDirectoryListModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] NeoChatConnection *connection() const;
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
|
||||
[[nodiscard]] QString searchText() const;
|
||||
void setSearchText(const QString &searchText);
|
||||
|
||||
[[nodiscard]] bool searching() const;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Search the user directory.
|
||||
*
|
||||
* @param limit the maximum number of rooms to load.
|
||||
*/
|
||||
Q_INVOKABLE void search(int limit = 50);
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void searchTextChanged();
|
||||
void searchingChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
QString m_searchText;
|
||||
|
||||
bool attempted = false;
|
||||
QList<Quotient::SearchUserDirectoryJob::User> users;
|
||||
|
||||
Quotient::SearchUserDirectoryJob *m_job = nullptr;
|
||||
};
|
||||
41
src/app/models/userfiltermodel.cpp
Normal file
41
src/app/models/userfiltermodel.cpp
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2022 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 "userfiltermodel.h"
|
||||
|
||||
#include "models/userlistmodel.h"
|
||||
|
||||
bool UserFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
Q_UNUSED(sourceParent);
|
||||
if (!m_allowEmpty && m_filterText.length() < 1) {
|
||||
return false;
|
||||
}
|
||||
return sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
|| sourceModel()->data(sourceModel()->index(sourceRow, 0), UserListModel::UserIdRole).toString().contains(m_filterText, Qt::CaseInsensitive);
|
||||
}
|
||||
|
||||
QString UserFilterModel::filterText() const
|
||||
{
|
||||
return m_filterText;
|
||||
}
|
||||
|
||||
void UserFilterModel::setFilterText(const QString &filterText)
|
||||
{
|
||||
m_filterText = filterText;
|
||||
Q_EMIT filterTextChanged();
|
||||
invalidateFilter();
|
||||
}
|
||||
|
||||
bool UserFilterModel::allowEmpty() const
|
||||
{
|
||||
return m_allowEmpty;
|
||||
}
|
||||
|
||||
void UserFilterModel::setAllowEmpty(bool allowEmpty)
|
||||
{
|
||||
m_allowEmpty = allowEmpty;
|
||||
Q_EMIT allowEmptyChanged();
|
||||
}
|
||||
|
||||
#include "moc_userfiltermodel.cpp"
|
||||
50
src/app/models/userfiltermodel.h
Normal file
50
src/app/models/userfiltermodel.h
Normal file
@@ -0,0 +1,50 @@
|
||||
// SPDX-FileCopyrightText: 2022 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 UserFilterModel
|
||||
*
|
||||
* This class creates a custom QSortFilterProxyModel for filtering a users by either
|
||||
* display name or matrix ID. The filter can accept a full matrix id i.e. example:kde.org
|
||||
* to separate between accounts on different servers with similar names.
|
||||
*/
|
||||
class UserFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
/**
|
||||
* @brief This property hold the text of the filter.
|
||||
*
|
||||
* The text is either a desired display name or matrix id.
|
||||
*/
|
||||
Q_PROPERTY(QString filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
Q_PROPERTY(bool allowEmpty READ allowEmpty WRITE setAllowEmpty NOTIFY allowEmptyChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Custom filter function checking boith the display name and matrix ID.
|
||||
*
|
||||
* @note The filter cannot be modified and will always use the same filter properties.
|
||||
*/
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
QString filterText() const;
|
||||
void setFilterText(const QString &filterText);
|
||||
|
||||
bool allowEmpty() const;
|
||||
void setAllowEmpty(bool allowEmpty);
|
||||
|
||||
Q_SIGNALS:
|
||||
void filterTextChanged();
|
||||
void allowEmptyChanged();
|
||||
|
||||
private:
|
||||
QString m_filterText;
|
||||
bool m_allowEmpty = false;
|
||||
};
|
||||
Reference in New Issue
Block a user