Compare commits

...

3 Commits

Author SHA1 Message Date
Tobias Fella
ce3915e22a Remove unneeded change 2023-04-11 16:02:47 +02:00
Tobias Fella
741f6ea659 Apply 1 suggestion(s) to 1 file(s) 2023-04-11 16:02:47 +02:00
Tobias Fella
310e9b7ba3 Remove compatibility with libQuotient 0.6 2023-04-11 16:02:45 +02:00
39 changed files with 676 additions and 1069 deletions

View File

@@ -68,12 +68,6 @@ set_package_properties(KF${QT_MAJOR_VERSION}Kirigami2 PROPERTIES
)
find_package(KF${QT_MAJOR_VERSION}KirigamiAddons 0.7.2 REQUIRED)
find_package(Qt${QT_MAJOR_VERSION}Keychain)
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
TYPE REQUIRED
PURPOSE "Secure storage of account secrets"
)
if(ANDROID)
find_package(OpenSSL)
set_package_properties(OpenSSL PROPERTIES
@@ -93,7 +87,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
find_package(KF${QT_MAJOR_VERSION}DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif()
find_package(Quotient 0.6)
find_package(Quotient 0.7)
set_package_properties(Quotient PROPERTIES
TYPE REQUIRED
DESCRIPTION "Qt wrapper around Matrix API"
@@ -131,9 +125,6 @@ set_package_properties(KF${QT_MAJOR_VERSION}DocTools PROPERTIES DESCRIPTION
TYPE OPTIONAL
)
if(NOT Quotient_VERSION_MINOR GREATER 6)
cmake_policy(SET CMP0063 OLD)
endif()
if(ANDROID)
find_package(Sqlite3)
@@ -150,7 +141,7 @@ install(FILES org.kde.neochat.tray.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/h
add_definitions(-DQT_NO_FOREACH)
add_subdirectory(src)
if (BUILD_TESTING AND Quotient_VERSION_MINOR GREATER 6)
if (BUILD_TESTING)
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
add_subdirectory(autotests)
endif()

View File

@@ -65,7 +65,6 @@ private Q_SLOTS:
void receiveRichEdited();
};
#ifdef QUOTIENT_07
void TextHandlerTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
@@ -198,7 +197,6 @@ void TextHandlerTest::initTestCase()
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
room->update(std::move(roomData));
}
#endif
void TextHandlerTest::allowedAttributes()
{
@@ -473,7 +471,6 @@ void TextHandlerTest::receiveRichtextIn()
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
}
#ifdef QUOTIENT_07
void TextHandlerTest::receiveRichMxcUrl()
{
const QString testInputString = QStringLiteral(
@@ -491,7 +488,6 @@ void TextHandlerTest::receiveRichMxcUrl()
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(0).get()), testOutputString);
}
#endif
/**
* For when your rich input string has a plain text url left in.

View File

@@ -30,7 +30,6 @@ add_library(neochat STATIC
models/devicesmodel.cpp
filetypesingleton.cpp
login.cpp
stickerevent.cpp
models/webshortcutmodel.cpp
blurhash.cpp
blurhashimageprovider.cpp
@@ -47,8 +46,12 @@ add_library(neochat STATIC
filetransferpseudojob.cpp
models/searchmodel.cpp
texthandler.cpp
pollevent.cpp
pollhandler.cpp
)
target_compile_definitions(neochat PUBLIC "-DQT_NO_KEYWORDS")
add_executable(neochat-app
main.cpp
res.qrc
@@ -60,14 +63,6 @@ target_link_libraries(neochat-app PRIVATE
neochat
)
if(Quotient_VERSION_MINOR GREATER 6)
target_compile_definitions(neochat PUBLIC QUOTIENT_07)
target_sources(neochat PRIVATE pollevent.cpp pollhandler.cpp)
else()
target_compile_definitions(neochat PUBLIC QUOTIENT_VERSION=\"${Quotient_VERSION}\")
target_sources(neochat PRIVATE neochataccountregistry.cpp)
endif()
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
@@ -95,6 +90,7 @@ endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
if(NEOCHAT_FLATPAK)

View File

@@ -31,11 +31,7 @@
#include <signal.h>
#ifdef QUOTIENT_07
#include "accountregistry.h"
#else
#include "neochataccountregistry.h"
#endif
#include <accountregistry.h>
#include <connection.h>
#include <csapi/content-repo.h>
@@ -43,11 +39,8 @@
#include <csapi/profile.h>
#include <jobs/downloadfilejob.h>
#include <qt_connection_util.h>
#ifdef QUOTIENT_07
#include <csapi/notifications.h>
#include <eventstats.h>
#endif
#include "neochatconfig.h"
#include "neochatroom.h"
@@ -56,6 +49,8 @@
#include "roommanager.h"
#include "windowcontroller.h"
#include <accountregistry.h>
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
#elif !defined(Q_OS_ANDROID)
@@ -93,10 +88,28 @@ Controller::Controller(QObject *parent)
});
#endif
QTimer::singleShot(0, this, [this] {
invokeLogin();
connectUntil(&Accounts, &AccountRegistry::rowsInserted, this, [this]() {
if (auto *connection = Accounts.get(NeoChatConfig::self()->activeConnection())) {
connectSingleShot(connection, &Connection::loadedRoomState, this, [this, connection]() {
setActiveConnection(connection);
});
return true;
}
return false;
});
connect(&Accounts, &AccountRegistry::rowsRemoved, this, [this]() {
if (!Accounts.isLoggedIn(NeoChatConfig::self()->activeConnection())) {
if (Accounts.size() > 0) {
setActiveConnection(Accounts.accounts().at(0));
} else {
setActiveConnection(nullptr);
}
}
});
QMetaObject::invokeMethod(&Accounts, &AccountRegistry::invokeLogin);
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
NeoChatConfig::self()->save();
});
@@ -125,23 +138,29 @@ Controller::Controller(QObject *parent)
}
#endif
connect(&AccountRegistry::instance(), &AccountRegistry::accountCountChanged, this, &Controller::activeConnectionIndexChanged);
connect(&Accounts, &AccountRegistry::accountCountChanged, this, &Controller::activeConnectionIndexChanged);
#ifdef QUOTIENT_07
static int oldAccountCount = 0;
connect(&AccountRegistry::instance(), &AccountRegistry::accountCountChanged, this, [=]() {
if (AccountRegistry::instance().size() > oldAccountCount) {
auto connection = AccountRegistry::instance().accounts()[AccountRegistry::instance().size() - 1];
connect(connection, &Connection::syncDone, this, [=]() {
handleNotifications(connection);
connect(&Accounts, &AccountRegistry::accountCountChanged, this, [this]() {
if (Accounts.size() > oldAccountCount) {
auto connection = Accounts.accounts()[Accounts.size() - 1];
connect(connection, &Connection::syncDone, this, [this, connection]() {
bool changes = false;
for (const auto &room : connection->allRooms()) {
if (m_notificationCounts[room] != room->unreadStats().notableCount) {
m_notificationCounts[room] = room->unreadStats().notableCount;
changes = true;
}
}
if (changes) {
handleNotifications(connection);
}
});
}
oldAccountCount = AccountRegistry::instance().size();
oldAccountCount = Accounts.size();
});
#endif
}
#ifdef QUOTIENT_07
void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
{
static QStringList initial;
@@ -215,7 +234,6 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
}
});
}
#endif
Controller &Controller::instance()
{
@@ -228,239 +246,10 @@ void Controller::showWindow()
WindowController::instance().showAndRaiseWindow(QString());
}
void Controller::loginWithAccessToken(const QString &serverAddr, const QString &user, const QString &token, const QString &deviceName)
{
if (user.isEmpty() || token.isEmpty()) {
return;
}
QUrl serverUrl(serverAddr);
auto conn = new Connection();
if (serverUrl.isValid()) {
conn->setHomeserver(serverUrl);
}
connect(conn, &Connection::connected, this, [this, conn, deviceName] {
AccountSettings account(conn->userId());
account.setKeepLoggedIn(true);
account.setHomeserver(conn->homeserver());
account.setDeviceId(conn->deviceId());
account.setDeviceName(deviceName);
if (!saveAccessTokenToKeyChain(account, conn->accessToken())) {
qWarning() << "Couldn't save access token";
}
account.sync();
addConnection(conn);
setActiveConnection(conn);
});
connect(conn, &Connection::networkError, this, [this](QString error, const QString &, int, int) {
Q_EMIT errorOccured(i18n("Network Error: %1", error));
});
conn->assumeIdentity(user, token, deviceName);
}
void Controller::logout(Connection *conn, bool serverSideLogout)
{
if (!conn) {
qCritical() << "Attempt to logout null connection";
return;
}
SettingsGroup("Accounts").remove(conn->userId());
QKeychain::DeletePasswordJob job(qAppName());
job.setAutoDelete(true);
job.setKey(conn->userId());
QEventLoop loop;
QKeychain::DeletePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (AccountRegistry::instance().count() > 1) {
// Only set the connection if the the account being logged out is currently active
if (conn == activeConnection()) {
setActiveConnection(AccountRegistry::instance().accounts()[0]);
}
} else {
setActiveConnection(nullptr);
}
if (!serverSideLogout) {
return;
}
conn->logout();
}
void Controller::addConnection(Connection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
#ifndef QUOTIENT_07
AccountRegistry::instance().add(c);
#endif
c->setLazyLoading(true);
connect(c, &Connection::syncDone, this, [this, c] {
setBusy(false);
Q_EMIT syncDone();
c->sync(30000);
c->saveState();
});
connect(c, &Connection::loggedOut, this, [this, c] {
dropConnection(c);
});
connect(c, &Connection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequiredError) {
Q_EMIT userConsentRequired(job->errorUrl());
}
});
setBusy(true);
c->sync();
Q_EMIT connectionAdded(c);
Q_EMIT accountCountChanged();
}
void Controller::dropConnection(Connection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
#ifndef QUOTIENT_07
AccountRegistry::instance().drop(c);
#endif
Q_EMIT connectionDropped(c);
Q_EMIT accountCountChanged();
#ifndef QUOTIENT_07
c->deleteLater();
#endif
}
void Controller::invokeLogin()
{
const auto accounts = SettingsGroup("Accounts").childGroups();
QString id = NeoChatConfig::self()->activeConnection();
for (const auto &accountId : accounts) {
AccountSettings account{accountId};
if (id.isEmpty()) {
// handle case where the account config is empty
id = accountId;
}
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
accessToken = accessTokenLoadingJob->binaryData();
} else {
return;
}
auto connection = new Connection(account.homeserver());
connect(connection, &Connection::connected, this, [this, connection, id] {
connection->loadState();
addConnection(connection);
if (connection->userId() == id) {
setActiveConnection(connection);
connectSingleShot(connection, &Connection::syncDone, this, &Controller::initiated);
}
});
connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) {
if (error == "Unrecognised access token") {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
logout(connection, false);
} else if (error == "Connection closed") {
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
// Failed due to network connection issue. This might happen when the homeserver is
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
// connect to the homeserver. In this case, we don't want to do logout().
} else {
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
logout(connection, true);
}
Q_EMIT initiated();
});
connect(connection, &Connection::networkError, this, [this](const QString &error, const QString &, int, int) {
Q_EMIT errorOccured(i18n("Network Error: %1", error));
});
connection->assumeIdentity(account.userId(), accessToken, account.deviceId());
});
}
}
if (accounts.isEmpty()) {
Q_EMIT initiated();
}
}
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
{
qDebug() << "Reading access token from the keychain for" << account.userId();
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
job->setKey(account.userId());
// Handling of errors
connect(job, &QKeychain::Job::finished, this, [this, &account, job]() {
if (job->error() == QKeychain::Error::NoError) {
return;
}
switch (job->error()) {
case QKeychain::EntryNotFound:
Q_EMIT globalErrorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
break;
case QKeychain::AccessDeniedByUser:
case QKeychain::AccessDenied:
Q_EMIT globalErrorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
break;
case QKeychain::NoBackendAvailable:
Q_EMIT globalErrorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
break;
case QKeychain::OtherError:
Q_EMIT globalErrorOccured(i18n("Unable to read access token"), job->errorString());
break;
default:
break;
}
});
job->start();
return job;
}
bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
{
qDebug() << "Save the access token to the keychain for " << account.userId();
QKeychain::WritePasswordJob job(qAppName());
job.setAutoDelete(false);
job.setKey(account.userId());
job.setBinaryData(accessToken);
QEventLoop loop;
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
job.start();
loop.exec();
if (job.error()) {
qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
return false;
}
return true;
}
void Controller::changeAvatar(Connection *conn, const QUrl &localFile)
{
auto job = conn->uploadFile(localFile.toLocalFile());
#ifdef QUOTIENT_07
if (isJobPending(job)) {
#else
if (isJobRunning(job)) {
#endif
connect(job, &BaseJob::success, this, [conn, job] {
conn->callApi<SetAvatarUrlJob>(conn->userId(), job->contentUri());
});
@@ -487,7 +276,7 @@ bool Controller::supportSystemTray() const
void Controller::changePassword(Connection *connection, const QString &currentPassword, const QString &newPassword)
{
NeochatChangePasswordJob *job = connection->callApi<NeochatChangePasswordJob>(newPassword, false);
auto *job = connection->callApi<NeochatChangePasswordJob>(newPassword, false);
connect(job, &BaseJob::result, this, [this, job, currentPassword, newPassword, connection] {
if (job->error() == 103) {
QJsonObject replyData = job->jsonData();
@@ -498,7 +287,7 @@ void Controller::changePassword(Connection *connection, const QString &currentPa
authData["user"] = connection->user()->id();
QJsonObject identifier = {{"type", "m.id.user"}, {"user", connection->user()->id()}};
authData["identifier"] = identifier;
NeochatChangePasswordJob *innerJob = connection->callApi<NeochatChangePasswordJob>(newPassword, false, authData);
auto *innerJob = connection->callApi<NeochatChangePasswordJob>(newPassword, false, authData);
connect(innerJob, &BaseJob::success, this, [this]() {
Q_EMIT passwordStatus(PasswordStatus::Success);
});
@@ -518,11 +307,7 @@ bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
User *localUser = connection->user();
QString decoded = avatarSource.path();
if (decoded.isEmpty()) {
#ifdef QUOTIENT_07
connection->callApi<SetAvatarUrlJob>(localUser->id(), avatarSource);
#else
connection->callApi<SetAvatarUrlJob>(localUser->id(), QString());
#endif
return true;
}
if (QImageReader(decoded).read().isNull()) {
@@ -533,11 +318,7 @@ bool Controller::setAvatar(Connection *connection, const QUrl &avatarSource)
}
NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth)
#ifdef QUOTIENT_07
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), "/_matrix/client/r0/account/password")
#else
: BaseJob(HttpVerb::Post, QStringLiteral("ChangePasswordJob"), QStringLiteral("/_matrix/client/r0/account/password"))
#endif
{
QJsonObject _data;
addParam<>(_data, QStringLiteral("new_password"), newPassword);
@@ -546,9 +327,12 @@ NeochatChangePasswordJob::NeochatChangePasswordJob(const QString &newPassword, b
setRequestData(_data);
}
int Controller::accountCount() const
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/matrix/client/r0/devices/%1").arg(deviceId).toLatin1())
{
return AccountRegistry::instance().count();
QJsonObject _data;
addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
setRequestData(std::move(_data));
}
bool Controller::quitOnLastWindowClosed()
@@ -629,18 +413,6 @@ void Controller::saveWindowGeometry()
WindowController::instance().saveGeometry();
}
NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth)
#ifdef QUOTIENT_07
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId).toLatin1())
#else
: Quotient::BaseJob(HttpVerb::Delete, QStringLiteral("DeleteDeviceJob"), QStringLiteral("/_matrix/client/r0/devices/%1").arg(deviceId))
#endif
{
QJsonObject _data;
addParam<IfNotEmpty>(_data, QStringLiteral("auth"), auth);
setRequestData(std::move(_data));
}
void Controller::createRoom(const QString &name, const QString &topic)
{
auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
@@ -651,7 +423,7 @@ void Controller::createRoom(const QString &name, const QString &topic)
this,
&Controller::roomAdded,
this,
[this](NeoChatRoom *room) {
[](NeoChatRoom *room) {
RoomManager::instance().enterRoom(room);
},
Qt::QueuedConnection);
@@ -731,11 +503,7 @@ QString Controller::plainText(QQuickTextDocument *document) const
bool Controller::encryptionSupported() const
{
#ifdef QUOTIENT_07
return Quotient::encryptionSupported();
#else
return false;
#endif
}
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
@@ -776,29 +544,10 @@ void Controller::setApplicationProxy()
int Controller::activeConnectionIndex() const
{
#ifdef QUOTIENT_07
auto result = std::find_if(Accounts.accounts().begin(), Accounts.accounts().end(), [this](const auto &it) {
return it == m_connection;
});
return result - Accounts.accounts().begin();
#else
for (int i = 0; i < AccountRegistry::instance().rowCount(); i++) {
if (AccountRegistry::instance().data(AccountRegistry::instance().index(i, 0), AccountRegistry::UserIdRole).toString() == m_connection->userId()) {
return i;
}
}
return 0;
#endif
}
int Controller::quotientMinorVersion() const
{
// TODO libQuotient 0.7: Replace with version function from libQuotient
#ifdef QUOTIENT_07
return 7;
#else
return 6;
#endif
}
bool Controller::isFlatpak() const

View File

@@ -23,15 +23,9 @@ class Connection;
class Room;
}
namespace QKeychain
{
class ReadPasswordJob;
}
class Controller : public QObject
{
Q_OBJECT
Q_PROPERTY(int accountCount READ accountCount NOTIFY accountCountChanged)
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
@@ -40,7 +34,6 @@ class Controller : public QObject
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
Q_PROPERTY(int quotientMinorVersion READ quotientMinorVersion CONSTANT)
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
public:
@@ -49,17 +42,10 @@ public:
void setActiveConnection(Quotient::Connection *connection);
[[nodiscard]] Quotient::Connection *activeConnection() const;
void addConnection(Quotient::Connection *c);
void dropConnection(Quotient::Connection *c);
Q_INVOKABLE void loginWithAccessToken(const QString &, const QString &, const QString &, const QString &);
Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString &currentPassword, const QString &newPassword);
Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource);
[[nodiscard]] int accountCount() const;
[[nodiscard]] static bool quitOnLastWindowClosed();
void setQuitOnLastWindowClosed(bool value);
@@ -68,8 +54,6 @@ public:
[[nodiscard]] bool supportSystemTray() const;
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
int activeConnectionIndex() const;
enum PasswordStatus {
@@ -100,7 +84,6 @@ public:
Q_INVOKABLE void setApplicationProxy();
int quotientMinorVersion() const;
bool isFlatpak() const;
private:
@@ -110,20 +93,15 @@ private:
bool m_busy = false;
TrayIcon *m_trayIcon = nullptr;
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
void loadSettings();
void saveSettings() const;
bool m_isOnline = true;
QMap<Quotient::Room *, int> m_notificationCounts;
bool hasWindowSystem() const;
#ifdef QUOTIENT_07
void handleNotifications(QPointer<Quotient::Connection> connection);
#endif
private Q_SLOTS:
void invokeLogin();
void showWindow();
Q_SIGNALS:
@@ -136,7 +114,6 @@ Q_SIGNALS:
void syncDone();
void connectionAdded(Quotient::Connection *_t1);
void connectionDropped(Quotient::Connection *_t1);
void accountCountChanged();
void initiated();
void notificationClicked(const QString &_t1, const QString &_t2);
void quitOnLastWindowClosedChanged();
@@ -154,13 +131,12 @@ Q_SIGNALS:
void roomAdded(NeoChatRoom *room);
public Q_SLOTS:
void logout(Quotient::Connection *conn, bool serverSideLogout);
void changeAvatar(Quotient::Connection *conn, const QUrl &localFile);
static void markAllMessagesAsRead(Quotient::Connection *conn);
void saveWindowGeometry();
};
// TODO libQuotient 0.7: Drop
// TODO libQuotient 0.?: Drop
class NeochatChangePasswordJob : public Quotient::BaseJob
{
public:

View File

@@ -25,6 +25,7 @@ void FileTransferPseudoJob::fileTransferProgress(QString id, qint64 progress, qi
void FileTransferPseudoJob::fileTransferCompleted(QString id, QUrl localFile)
{
Q_UNUSED(localFile);
if (id != m_eventId) {
return;
}

View File

@@ -7,30 +7,17 @@
namespace Quotient
{
#ifdef QUOTIENT_07
class JoinRulesEvent : public StateEvent
#else
class JoinRulesEvent : public StateEventBase
#endif
{
public:
#ifdef QUOTIENT_07
QUO_EVENT(JoinRulesEvent, "m.room.join_rules")
#else
DEFINE_EVENT_TYPEID("m.room.join_rules", JoinRulesEvent)
#endif
explicit JoinRulesEvent(const QJsonObject &obj)
#ifdef QUOTIENT_07
: StateEvent(obj)
#else
: StateEventBase(typeId(), obj)
#endif
{
}
QString joinRule() const;
QJsonArray allow() const;
};
REGISTER_EVENT_TYPE(JoinRulesEvent)
}

View File

@@ -3,16 +3,13 @@
#include "login.h"
#ifdef QUOTIENT_07
#include <accountregistry.h>
#else
#include "neochataccountregistry.h"
#endif
#include <connection.h>
#include <qt_connection_util.h>
#include "controller.h"
#include "neochatroom.h"
#include <KLocalizedString>
@@ -43,7 +40,7 @@ void Login::init()
return;
}
m_isLoggedIn = AccountRegistry::instance().isLoggedIn(m_matrixId);
m_isLoggedIn = Accounts.isLoggedIn(m_matrixId);
Q_EMIT isLoggedInChanged();
if (m_isLoggedIn) {
return;
@@ -74,11 +71,7 @@ void Login::init()
account.setHomeserver(m_connection->homeserver());
account.setDeviceId(m_connection->deviceId());
account.setDeviceName(m_deviceName);
if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
qWarning() << "Couldn't save access token";
}
account.sync();
Controller::instance().addConnection(m_connection);
Controller::instance().setActiveConnection(m_connection);
m_connection = nullptr;
});
@@ -97,8 +90,9 @@ void Login::init()
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
});
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
Q_EMIT Controller::instance().initiated();
connectSingleShot(m_connection, &Connection::loadedRoomState, this, [this]() {
Controller::instance().setActiveConnection(m_connection);
// TODO close settings window
});
}

View File

@@ -28,15 +28,14 @@
#include "neochat-version.h"
#ifdef QUOTIENT_07
#include <accountregistry.h>
#else
#include "neochataccountregistry.h"
#endif
#include <networkaccessmanager.h>
#include <room.h>
#include <util.h>
#include <keyverificationsession.h>
#include <accountregistry.h>
#include <room.h>
#include <networkaccessmanager.h>
#include "actionshandler.h"
#include "blurhashimageprovider.h"
@@ -68,16 +67,11 @@
#include "neochatroom.h"
#include "neochatuser.h"
#include "notificationsmanager.h"
#ifdef QUOTIENT_07
#include "pollhandler.h"
#endif
#include "roommanager.h"
#include "spacehierarchycache.h"
#include "urlhelper.h"
#include "windowcontroller.h"
#ifdef QUOTIENT_07
#include <keyverificationsession.h>
#endif
#ifdef HAVE_COLORSCHEME
#include "colorschemer.h"
#endif
@@ -85,6 +79,7 @@
#include "models/statemodel.h"
#include "neochatuser.h"
#ifdef HAVE_RUNNER
#include "runner.h"
#include <QDBusConnection>
@@ -165,14 +160,10 @@ int main(int argc, char *argv[])
about.addComponent(QStringLiteral("libQuotient"),
i18n("A Qt5 library to write cross-platform clients for Matrix"),
#ifdef QUOTIENT_07
i18nc("<version number> (built against <possibly different version number>)",
"%1 (built against %2)",
Quotient::versionString(),
QStringLiteral(Quotient_VERSION_STRING)),
#else
QStringLiteral(QUOTIENT_VERSION),
#endif
QStringLiteral("https://github.com/quotient-im/libquotient"),
KAboutLicense::LGPL_V2_1);
@@ -209,11 +200,8 @@ int main(int argc, char *argv[])
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "LoginHelper", login);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "UrlHelper", &urlHelper);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "EmojiModel", &EmojiModel::instance());
#ifdef QUOTIENT_07
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Quotient::Accounts);
#else
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "AccountRegistry", &Quotient::AccountRegistry::instance());
#endif
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Accounts", &Quotient::Accounts);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "Accounts", &Quotient::Accounts);
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "SpaceHierarchyCache", &SpaceHierarchyCache::instance());
qmlRegisterSingletonInstance("org.kde.neochat", 1, 0, "CustomEmojiModel", &CustomEmojiModel::instance());
qmlRegisterType<ActionsHandler>("org.kde.neochat", 1, 0, "ActionsHandler");
@@ -235,9 +223,7 @@ int main(int argc, char *argv[])
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
#ifdef QUOTIENT_07
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
#endif
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
@@ -256,12 +242,10 @@ int main(int argc, char *argv[])
qRegisterMetaType<NeoChatUser *>("NeoChatUser*");
qRegisterMetaType<GetRoomEventsJob *>("GetRoomEventsJob*");
qRegisterMetaType<QMimeType>("QMimeType");
#ifdef QUOTIENT_07
#ifdef Quotient_E2EE_ENABLED
qRegisterMetaType<KeyVerificationSession *>("KeyVerificationSession*");
qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
qRegisterMetaType<QVector<EmojiEntry>>("QVector<EmojiEntry>");
#endif
#endif
qmlRegisterSingletonType("org.kde.neochat", 1, 0, "About", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
return engine->toScriptValue(KAboutData::applicationData());

View File

@@ -192,7 +192,6 @@ QVector<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
return QString();
}
#ifdef QUOTIENT_07
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
@@ -202,7 +201,6 @@ QVector<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
return QString();
}
#endif
if (room->localUser()->id() == text) {
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18n("You are already in this room."));
return QString();
@@ -244,7 +242,6 @@ QVector<ActionsModel::Action> actions{
kli18n("<room alias or id>"),
kli18n("Joins the given room"),
},
#ifdef QUOTIENT_07
Action{
QStringLiteral("knock"),
[](const QString &text, NeoChatRoom *room) {
@@ -277,7 +274,6 @@ QVector<ActionsModel::Action> actions{
kli18n("<room alias or id> [<reason>]"),
kli18n("Requests to join the given room"),
},
#endif
Action{
QStringLiteral("j"),
[](const QString &text, NeoChatRoom *room) {
@@ -436,14 +432,12 @@ QVector<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
return QString();
}
#ifdef QUOTIENT_07
auto state = room->currentState().get<RoomMemberEvent>(parts[0]);
if (state && state->membership() == Membership::Ban) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
return QString();
}
#endif
auto plEvent = room->getCurrentState<RoomPowerLevelsEvent>();
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to ban users from this room."));
return QString();
@@ -473,18 +467,16 @@ QVector<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
return QString();
}
auto plEvent = room->getCurrentState<RoomPowerLevelsEvent>();
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
if (plEvent->ban() > plEvent->powerLevelForUser(room->localUser()->id())) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to unban users from this room."));
return QString();
}
#ifdef QUOTIENT_07
auto state = room->currentState().get<RoomMemberEvent>(text);
if (state && state->membership() != Membership::Ban) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
return QString();
}
#endif
room->unban(text);
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
@@ -511,13 +503,11 @@ QVector<ActionsModel::Action> actions{
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You cannot kick yourself from the room."));
return QString();
}
#ifdef QUOTIENT_07
if (!room->isMember(parts[0])) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
return QString();
}
#endif
auto plEvent = room->getCurrentState<RoomPowerLevelsEvent>();
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
auto kick = plEvent->kick();
if (plEvent->powerLevelForUser(room->localUser()->id()) < kick) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to kick users from this room."));

View File

@@ -11,12 +11,6 @@
using namespace Quotient;
#ifdef QUOTIENT_07
#define running isJobPending
#else
#define running isJobRunning
#endif
void CustomEmojiModel::fetchEmojis()
{
if (!Controller::instance().activeConnection()) {
@@ -57,18 +51,12 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
if (running(job)) {
connect(job, &BaseJob::success, this, [this, name, job] {
if (isJobPending(job)) {
connect(job, &BaseJob::success, this, [name, job] {
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes");
auto json = data != nullptr ? data->contentJson() : QJsonObject();
auto emojiData = json["images"].toObject();
emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({
#ifdef QUOTIENT_07
{QStringLiteral("url"), job->contentUri().toString()}
#else
{QStringLiteral("url"), job->contentUri()}
#endif
});
emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({{QStringLiteral("url"), job->contentUri().toString()}});
json["images"] = emojiData;
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes", json);
});
@@ -141,8 +129,9 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
case Roles::MxcUrl:
return data.url.mid(6);
default:
return {};
}
return QVariant();
}

View File

@@ -67,7 +67,7 @@ QHash<int, QByteArray> DevicesModel::roleNames() const
void DevicesModel::logout(int index, const QString &password)
{
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
auto job = Controller::instance().activeConnection()->callApi<DeleteDeviceJob>(m_devices[index].deviceId);
connect(job, &BaseJob::result, this, [this, job, password, index] {
auto onSuccess = [this, index]() {

View File

@@ -11,12 +11,10 @@
#include <events/roomavatarevent.h>
#include <events/roommemberevent.h>
#include <events/simplestateevents.h>
#include <events/stickerevent.h>
#include <user.h>
#ifdef QUOTIENT_07
#include "pollevent.h"
#endif
#include "stickerevent.h"
#include <QDebug>
#include <QGuiApplication>
@@ -109,11 +107,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
room->getPreviousContent(50);
}
#ifdef QUOTIENT_07
lastReadEventId = room->lastFullyReadEventId();
#else
lastReadEventId = room->readMarkerEventId();
#endif
using namespace Quotient;
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
@@ -162,11 +156,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
endInsertRows();
if (!m_lastReadEventIndex.isValid()) {
// no read marker, so see if we need to create one.
#ifdef QUOTIENT_07
moveReadMarker(m_currentRoom->lastFullyReadEventId());
#else
moveReadMarker(m_currentRoom->readMarkerEventId());
#endif
}
if (biggest < m_currentRoom->maxTimelineIndex()) {
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
@@ -207,7 +197,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
beginRemoveRows({}, i, i);
});
connect(m_currentRoom, &Room::pendingEventDiscarded, this, &MessageEventModel::endRemoveRows);
connect(m_currentRoom, &Room::readMarkerMoved, this, [this](const QString &fromEventId, const QString &toEventId) {
connect(m_currentRoom, &Room::fullyReadMarkerMoved, this, [this](const QString &fromEventId, const QString &toEventId) {
Q_UNUSED(fromEventId);
moveReadMarker(toEventId);
});
@@ -230,9 +220,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
connect(m_currentRoom, &Room::fileTransferProgress, this, &MessageEventModel::refreshEvent);
connect(m_currentRoom, &Room::fileTransferCompleted, this, &MessageEventModel::refreshEvent);
connect(m_currentRoom, &Room::fileTransferFailed, this, &MessageEventModel::refreshEvent);
#ifndef QUOTIENT_07
connect(m_currentRoom, &Room::fileTransferCancelled, this, &MessageEventModel::refreshEvent);
#endif
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [this] {
beginResetModel();
endResetModel();
@@ -498,7 +485,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == SourceRole) {
return evt.originalJson();
return QJsonDocument(evt.fullJson()).toJson();
}
if (role == DelegateTypeRole) {
@@ -534,20 +521,18 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
if (is<const EncryptedEvent>(evt)) {
return DelegateType::Encrypted;
}
#ifdef QUOTIENT_07
if (is<PollStartEvent>(evt)) {
if (evt.isRedacted()) {
return DelegateType::Message;
}
return DelegateType::Poll;
}
#endif
return DelegateType::Other;
}
if (role == EventResolvedTypeRole) {
return EventTypeRegistry::getMatrixType(evt.type());
return evt.type();
}
if (role == AuthorRole) {
@@ -646,7 +631,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
return EventStatus::Hidden;
}
if (evt.isStateEvent() && static_cast<const StateEventBase &>(evt).repeatsState()) {
if (evt.isStateEvent() && static_cast<const StateEvent &>(evt).repeatsState()) {
return EventStatus::Hidden;
}
@@ -820,22 +805,13 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == ReadMarkersRole) {
#ifdef QUOTIENT_07
auto userIds = room()->userIdsAtEvent(evt.id());
userIds.remove(m_currentRoom->localUser()->id());
#else
auto userIds = room()->usersAtEventId(evt.id());
userIds.removeAll(m_currentRoom->localUser());
#endif
QVariantList users;
users.reserve(userIds.size());
for (const auto &userId : userIds) {
#ifdef QUOTIENT_07
auto user = static_cast<NeoChatUser *>(m_currentRoom->user(userId));
#else
auto user = static_cast<NeoChatUser *>(userId);
#endif
users += userAtEvent(user, m_currentRoom, evt);
}
@@ -843,24 +819,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == ReadMarkersStringRole) {
#ifdef QUOTIENT_07
auto userIds = room()->userIdsAtEvent(evt.id());
userIds.remove(m_currentRoom->localUser()->id());
#else
auto userIds = room()->usersAtEventId(evt.id());
userIds.removeAll(m_currentRoom->localUser());
#endif
/**
* The string ends up in the form
* "x users: user1DisplayName, user2DisplayName, etc."
*/
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
for (const auto &userId : userIds) {
#ifdef QUOTIENT_07
auto user = static_cast<NeoChatUser *>(m_currentRoom->user(userId));
#else
auto user = static_cast<NeoChatUser *>(userId);
#endif
readMarkersString += user->displayname(m_currentRoom) + i18nc("list separator", ", ");
}
readMarkersString.chop(2);
@@ -868,18 +835,13 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == ShowReadMarkersRole) {
#ifdef QUOTIENT_07
auto userIds = room()->userIdsAtEvent(evt.id());
userIds.remove(m_currentRoom->localUser()->id());
#else
auto userIds = room()->usersAtEventId(evt.id());
userIds.removeAll(m_currentRoom->localUser());
#endif
return userIds.size() > 0;
}
if (role == ReactionRole) {
const auto &annotations = m_currentRoom->relatedEvents(evt, EventRelation::Annotation());
const auto &annotations = m_currentRoom->relatedEvents(evt, EventRelation::AnnotationType);
if (annotations.isEmpty()) {
return {};
};
@@ -889,7 +851,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
continue;
}
if (auto e = eventCast<const ReactionEvent>(a)) {
reactions[e->relation().key].append(static_cast<NeoChatUser *>(m_currentRoom->user(e->senderId())));
reactions[e->eventId()].append(static_cast<NeoChatUser *>(m_currentRoom->user(e->senderId())));
}
}
@@ -916,7 +878,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == MediaUrlRole) {
#ifdef QUOTIENT_07
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
if (!e->hasFileContent()) {
return QVariant();
@@ -933,7 +894,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
// if (auto e = eventCast<const StickerEvent>(&evt)) {
// return m_currentRoom->makeMediaUrl(e->id(), e->url());
// }
#endif
// Construct link in the same form as urlToDownload as that function doesn't work for stickers
if (auto e = eventCast<const StickerEvent>(&evt)) {
@@ -948,14 +908,12 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == VerifiedRole) {
#ifdef QUOTIENT_07
#ifdef Quotient_E2EE_ENABLED
if (evt.originalEvent()) {
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
Q_ASSERT(encrypted);
return m_currentRoom->connection()->isVerifiedSession(encrypted->sessionId().toLatin1());
}
#endif
#endif
return false;
}
@@ -983,7 +941,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
}
if (role == IsPendingRole) {
return row < m_currentRoom->pendingEvents().size();
return row < static_cast<int>(m_currentRoom->pendingEvents().size());
}
return {};

View File

@@ -117,11 +117,7 @@ void PublicRoomListModel::next(int count)
return;
}
#ifdef QUOTIENT_07
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword, {}});
#else
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword});
#endif
connect(job, &BaseJob::finished, this, [this] {
attempted = true;
@@ -177,11 +173,7 @@ QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
if (avatarUrl.isEmpty()) {
return "";
}
#ifdef QUOTIENT_07
return avatarUrl.url().remove(0, 6);
#else
return avatarUrl.remove(0, 6);
#endif
}
if (role == TopicRole) {
return room.topic;

View File

@@ -8,6 +8,7 @@
#include "neochatroom.h"
#include "roommanager.h"
#include "user.h"
#include <eventstats.h>
#include <QDebug>
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
@@ -22,11 +23,6 @@
#include <QGuiApplication>
#include <utility>
#ifndef QUOTIENT_07
#include "notificationsmanager.h"
#include <csapi/notifications.h>
#endif
using namespace Quotient;
Q_DECLARE_METATYPE(Quotient::JoinState)
@@ -151,7 +147,7 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
connect(room, &Room::displaynameChanged, this, [this, room] {
refresh(room, {DisplayNameRole, NameRole});
});
connect(room, &Room::unreadMessagesChanged, this, [this, room] {
connect(room, &Room::unreadStatsChanged, this, [this, room] {
refresh(room, {UnreadCountRole, NotificationCountRole, HighlightCountRole});
});
connect(room, &Room::notificationCountChanged, this, [this, room] {
@@ -172,71 +168,9 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
connect(room, &Room::pendingEventMerged, this, [this, room] {
refresh(room, {LastEventRole, SubtitleTextRole});
});
#ifndef QUOTIENT_07
connect(room, &Room::notificationCountChanged, this, &RoomListModel::handleNotifications);
#endif
#ifndef QUOTIENT_07
connect(room, &Room::notificationCountChanged, this, &RoomListModel::refreshNotificationCount);
#else
connect(room, &Room::unreadStatsChanged, this, &RoomListModel::refreshNotificationCount);
#endif
}
#ifndef QUOTIENT_07
void RoomListModel::handleNotifications()
{
static bool initial = true;
static QStringList oldNotifications;
auto job = m_connection->callApi<GetNotificationsJob>();
connect(job, &BaseJob::success, this, [this, job]() {
const auto notifications = job->jsonData()["notifications"].toArray();
if (initial) {
initial = false;
for (const auto &n : notifications) {
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
}
return;
}
for (const auto &n : notifications) {
const auto notification = n.toObject();
if (notification["read"].toBool()) {
oldNotifications.removeOne(notification["event"].toObject()["event_id"].toString());
continue;
}
if (oldNotifications.contains(notification["event"].toObject()["event_id"].toString())) {
continue;
}
oldNotifications += notification["event"].toObject()["event_id"].toString();
auto room = m_connection->room(notification["room_id"].toString());
auto currentRoom = RoomManager::instance().currentRoom();
bool roomIsActive = currentRoom && room->id() == currentRoom->id();
// If room exists, room is NOT active OR the application is NOT active, show notification
if (room && !(roomIsActive && QGuiApplication::applicationState() == Qt::ApplicationActive)) {
// The room might have been deleted (for example rejected invitation).
auto sender = room->user(notification["event"].toObject()["sender"].toString());
QImage avatar_image;
if (!sender->avatarUrl(room).isEmpty()) {
avatar_image = sender->avatar(128, room);
} else {
avatar_image = room->avatar(128);
}
NotificationsManager::instance().postNotification(dynamic_cast<NeoChatRoom *>(room),
sender->displayname(room),
notification["event"].toObject()["content"].toObject()["body"].toString(),
avatar_image,
notification["event"].toObject()["event_id"].toString(),
true);
}
}
});
}
#endif
void RoomListModel::refreshNotificationCount()
{
int count = 0;
@@ -361,7 +295,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
return NeoChatRoomType::Normal;
}
if (role == UnreadCountRole) {
return room->unreadCount();
return room->unreadStats().notableCount;
}
if (role == NotificationCountRole) {
return room->notificationCount();

View File

@@ -107,9 +107,6 @@ private:
QString m_activeSpaceId = "";
void connectRoomSignals(NeoChatRoom *room);
#ifndef QUOTIENT_07
void handleNotifications();
#endif
Q_SIGNALS:
void connectionChanged();

View File

@@ -8,9 +8,7 @@
#include <KLocalizedString>
#include <connection.h>
#ifdef QUOTIENT_07
#include <csapi/search.h>
#endif
using namespace Quotient;
@@ -34,7 +32,6 @@ void SearchModel::setSearchText(const QString &searchText)
void SearchModel::search()
{
#ifdef QUOTIENT_07
Q_ASSERT(m_connection);
setSearching(true);
if (m_job) {
@@ -43,20 +40,26 @@ void SearchModel::search()
}
SearchJob::RoomEventsCriteria criteria{
m_searchText,
{},
RoomEventFilter{
.rooms = {m_room->id()},
},
"recent",
SearchJob::IncludeEventContext{3, 3, true},
false,
none,
.searchTerm = m_searchText,
.keys = {},
.filter =
RoomEventFilter{
.unreadThreadNotifications = none,
.lazyLoadMembers = true,
.includeRedundantMembers = false,
.notRooms = {},
.rooms = {m_room->id()},
.containsUrl = false,
},
.orderBy = "recent",
.eventContext = SearchJob::IncludeEventContext{3, 3, true},
.includeState = false,
.groupings = none,
};
auto job = m_connection->callApi<SearchJob>(SearchJob::Categories{criteria});
m_job = job;
connect(job, &BaseJob::finished, this, [=] {
connect(job, &BaseJob::finished, this, [this, job] {
beginResetModel();
m_result = job->searchCategories().roomEvents;
endResetModel();
@@ -64,7 +67,6 @@ void SearchModel::search()
m_job = nullptr;
// TODO error handling
});
#endif
}
Connection *SearchModel::connection() const
@@ -80,7 +82,6 @@ void SearchModel::setConnection(Connection *connection)
QVariant SearchModel::data(const QModelIndex &index, int role) const
{
#ifdef QUOTIENT_07
auto row = index.row();
const auto &event = *m_result->results[row].result;
switch (role) {
@@ -110,17 +111,14 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
return event.originTimestamp();
}
return MessageEventModel::DelegateType::Message;
#endif
return {};
}
int SearchModel::rowCount(const QModelIndex &parent) const
{
#ifdef QUOTIENT_07
Q_UNUSED(parent);
if (m_result.has_value()) {
return m_result->results.size();
}
#endif
return 0;
}

View File

@@ -6,9 +6,7 @@
#include <QAbstractListModel>
#include <QString>
#ifdef QUOTIENT_07
#include <csapi/search.h>
#endif
namespace Quotient
{
@@ -68,10 +66,8 @@ private:
QString m_searchText;
Quotient::Connection *m_connection = nullptr;
NeoChatRoom *m_room = nullptr;
#ifdef QUOTIENT_07
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
Quotient::SearchJob *m_job = nullptr;
#endif
bool m_searching = false;
};

View File

@@ -95,11 +95,7 @@ void ServerListModel::checkServer(const QString &url)
KConfigGroup serverGroup(&dataResource, "Servers");
if (!serverGroup.hasKey(url)) {
#ifdef QUOTIENT_07
if (Quotient::isJobPending(m_checkServerJob)) {
#else
if (Quotient::isJobRunning(m_checkServerJob)) {
#endif
m_checkServerJob->abandon();
}

View File

@@ -14,7 +14,6 @@ QHash<int, QByteArray> StateModel::roleNames() const
}
QVariant StateModel::data(const QModelIndex &index, int role) const
{
#ifdef QUOTIENT_07
auto row = index.row();
switch (role) {
case TypeRole:
@@ -24,18 +23,13 @@ QVariant StateModel::data(const QModelIndex &index, int role) const
case SourceRole:
return QJsonDocument(m_room->currentState().events()[m_room->currentState().events().keys()[row]]->fullJson()).toJson();
}
#endif
return {};
}
int StateModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
#ifdef QUOTIENT_07
return m_room->currentState().events().size();
#else
return 0;
#endif
}
NeoChatRoom *StateModel::room() const
@@ -49,7 +43,7 @@ void StateModel::setRoom(NeoChatRoom *room)
Q_EMIT roomChanged();
beginResetModel();
endResetModel();
connect(room, &NeoChatRoom::changed, this, [=] {
connect(room, &NeoChatRoom::changed, this, [this] {
beginResetModel();
endResetModel();
});

View File

@@ -131,11 +131,7 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
if (avatarUrl.isEmpty()) {
return "";
}
#ifdef QUOTIENT_07
return avatarUrl.url().remove(0, 6);
#else
return avatarUrl.remove(0, 6);
#endif
}
if (role == UserIDRole) {
return user.userId;

View File

@@ -44,13 +44,9 @@ void UserListModel::setRoom(NeoChatRoom *room)
std::sort(m_users.begin(), m_users.end(), room->memberSorter());
}
for (User *user : std::as_const(m_users)) {
#ifdef QUOTIENT_07
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
avatarChanged(user, m_currentRoom);
});
#else
connect(user, &User::avatarChanged, this, &UserListModel::avatarChanged);
#endif
}
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
setRoom(nullptr);
@@ -96,15 +92,14 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue(user);
}
if (role == PowerLevelRole) {
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
if (!pl) {
return 0;
}
return pl->powerLevelForUser(user->id());
}
if (role == PowerLevelStringRole) {
#ifdef QUOTIENT_07
auto pl = m_currentRoom->currentState().get<RoomPowerLevelsEvent>();
#else
auto pl = m_currentRoom->getCurrentState<RoomPowerLevelsEvent>();
#endif
// User might not in the room yet, in this case pl can be nullptr.
// e.g. When invited but user not accepted or denied the invitation.
if (!pl) {
@@ -143,13 +138,9 @@ void UserListModel::userAdded(Quotient::User *user)
beginInsertRows(QModelIndex(), pos, pos);
m_users.insert(pos, user);
endInsertRows();
#ifdef QUOTIENT_07
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
avatarChanged(user, m_currentRoom);
});
#else
connect(user, &Quotient::User::avatarChanged, this, &UserListModel::avatarChanged);
#endif
}
void UserListModel::userRemoved(Quotient::User *user)
@@ -188,13 +179,9 @@ void UserListModel::refreshAll()
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
}
for (User *user : std::as_const(m_users)) {
#ifdef QUOTIENT_07
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
avatarChanged(user, m_currentRoom);
});
#else
connect(user, &User::avatarChanged, this, &UserListModel::avatarChanged);
#endif
}
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
setRoom(nullptr);

View File

@@ -1,105 +0,0 @@
// SPDX-FileCopyrightText: Kitsune Ral <Kitsune-Ral@users.sf.net>
// SPDX-FileCopyrightText: Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "neochataccountregistry.h"
#include <connection.h>
using namespace Quotient;
void AccountRegistry::add(Connection *c)
{
if (m_accounts.contains(c))
return;
beginInsertRows(QModelIndex(), m_accounts.size(), m_accounts.size());
m_accounts += c;
endInsertRows();
emit accountCountChanged();
}
void AccountRegistry::drop(Connection *c)
{
beginRemoveRows(QModelIndex(), m_accounts.indexOf(c), m_accounts.indexOf(c));
m_accounts.removeOne(c);
endRemoveRows();
Q_ASSERT(!m_accounts.contains(c));
emit accountCountChanged();
}
bool AccountRegistry::isLoggedIn(const QString &userId) const
{
return std::any_of(m_accounts.cbegin(), m_accounts.cend(), [&userId](Connection *a) {
return a->userId() == userId;
});
}
bool AccountRegistry::contains(Connection *c) const
{
return m_accounts.contains(c);
}
AccountRegistry::AccountRegistry() = default;
QVariant AccountRegistry::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
if (index.row() >= m_accounts.count()) {
return {};
}
const auto account = m_accounts[index.row()];
switch (role) {
case ConnectionRole:
return QVariant::fromValue(account);
case UserIdRole:
return QVariant::fromValue(account->userId());
default:
return {};
}
return {};
}
int AccountRegistry::rowCount(const QModelIndex &parent) const
{
if (parent.isValid()) {
return 0;
}
return m_accounts.count();
}
QHash<int, QByteArray> AccountRegistry::roleNames() const
{
return {{ConnectionRole, "connection"}, {UserIdRole, "userId"}};
}
bool AccountRegistry::isEmpty() const
{
return m_accounts.isEmpty();
}
int AccountRegistry::count() const
{
return m_accounts.count();
}
const QVector<Connection *> AccountRegistry::accounts() const
{
return m_accounts;
}
Connection *AccountRegistry::get(const QString &userId)
{
for (const auto &connection : m_accounts) {
if (connection->userId() == userId) {
return connection;
}
}
return nullptr;
}

View File

@@ -1,54 +0,0 @@
// SPDX-FileCopyrightText: 2020 Kitsune Ral <Kitsune-Ral@users.sf.net>
// SPDX-FileCopyrightText: Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <QAbstractListModel>
#include <QList>
#include <QObject>
namespace Quotient
{
class Connection;
class AccountRegistry : public QAbstractListModel
{
Q_OBJECT
Q_PROPERTY(int accountCount READ count NOTIFY accountCountChanged);
public:
enum EventRoles {
ConnectionRole = Qt::UserRole + 1,
UserIdRole = Qt::DisplayRole,
};
static AccountRegistry &instance()
{
static AccountRegistry _instance;
return _instance;
}
const QVector<Connection *> accounts() const;
void add(Connection *a);
void drop(Connection *a);
bool isLoggedIn(const QString &userId) const;
bool isEmpty() const;
int count() const;
bool contains(Connection *) const;
Connection *get(const QString &userId);
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void accountCountChanged();
private:
AccountRegistry();
QVector<Connection *> m_accounts;
};
}

View File

@@ -24,6 +24,7 @@
#include <csapi/room_state.h>
#include <csapi/typing.h>
#include <events/encryptionevent.h>
#include <events/eventrelation.h>
#include <events/reactionevent.h>
#include <events/redactionevent.h>
#include <events/roomavatarevent.h>
@@ -31,23 +32,19 @@
#include <events/roommemberevent.h>
#include <events/roompowerlevelsevent.h>
#include <events/simplestateevents.h>
#include <events/stickerevent.h>
#include <eventstats.h>
#include <jobs/downloadfilejob.h>
#ifndef QUOTIENT_07
#include <joinstate.h>
#endif
#include <qt_connection_util.h>
#include "controller.h"
#include "filetransferpseudojob.h"
#include "joinrulesevent.h"
#include "neochatconfig.h"
#include "neochatuser.h"
#include "notificationsmanager.h"
#ifdef QUOTIENT_07
#include "pollevent.h"
#include "pollhandler.h"
#endif
#include "filetransferpseudojob.h"
#include "stickerevent.h"
#include "texthandler.h"
#ifndef Q_OS_ANDROID
@@ -85,7 +82,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
if (this->joinState() != JoinState::Invite) {
return;
}
const QString senderId = getCurrentState<RoomMemberEvent>(localUser()->id())->senderId();
const QString senderId = currentState().get<RoomMemberEvent>(localUser()->id())->senderId();
QImage avatar_image;
if (!user(senderId)->avatarUrl(this).isEmpty()) {
avatar_image = user(senderId)->avatar(128, this);
@@ -174,17 +171,9 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
} else {
content = new EventContent::FileContent(url, fileInfo.size(), mime, fileInfo.fileName());
}
#ifdef QUOTIENT_07
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, content);
#else
QString txnId = postFile(body.isEmpty() ? url.fileName() : body, url, false);
#endif
setHasFileUploading(true);
#ifdef QUOTIENT_07
connect(this, &Room::fileTransferCompleted, [this, txnId](const QString &id, FileSourceInfo) {
#else
connect(this, &Room::fileTransferCompleted, [this, txnId](const QString &id, const QUrl & /*localFile*/, const QUrl & /*mxcUrl*/) {
#endif
if (id == txnId) {
setFileUploadingProgress(0);
setHasFileUploading(false);
@@ -254,21 +243,20 @@ const RoomEvent *NeoChatRoom::lastEvent() const
continue;
}
if (event->isStateEvent() && !NeoChatConfig::self()->showStateEvent()) {
if (event->isStateEvent() && !NeoChatConfig::showStateEvent()) {
continue;
}
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::self()->showLeaveJoinEvent()) {
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::showLeaveJoinEvent()) {
continue;
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::self()->showRename()) {
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::showRename()) {
continue;
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave()
&& !NeoChatConfig::self()->showAvatarUpdate()) {
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::showAvatarUpdate()) {
continue;
}
}
if (event->isStateEvent() && static_cast<const StateEventBase &>(*event).repeatsState()) {
if (event->isStateEvent() && static_cast<const StateEvent &>(*event).repeatsState()) {
continue;
}
@@ -293,11 +281,9 @@ const RoomEvent *NeoChatRoom::lastEvent() const
if (auto lastEvent = eventCast<const RoomMessageEvent>(event)) {
return lastEvent;
}
#ifdef QUOTIENT_07
if (auto lastEvent = eventCast<const PollStartEvent>(event)) {
return lastEvent;
}
#endif
}
return nullptr;
}
@@ -318,10 +304,9 @@ bool NeoChatRoom::lastEventIsSpoiler() const
QString NeoChatRoom::lastEventToString(Qt::TextFormat format, bool stripNewlines) const
{
if (auto event = lastEvent()) {
return roomMembername(event->senderId()) + (event->isStateEvent() ? QLatin1String(" ") : QLatin1String(": "))
+ eventToString(*event, format, stripNewlines);
return safeMemberName(event->senderId()) + (event->isStateEvent() ? " " : ": ") + eventToString(*event, format, stripNewlines);
}
return QLatin1String("");
return {};
}
bool NeoChatRoom::isEventHighlighted(const RoomEvent *e) const
@@ -337,7 +322,7 @@ void NeoChatRoom::checkForHighlights(const Quotient::TimelineItem &ti)
}
if (auto *e = ti.viewAs<RoomMessageEvent>()) {
const auto &text = e->plainBody();
if (text.contains(localUserId) || text.contains(roomMembername(localUserId))) {
if (text.contains(localUserId) || text.contains(safeMemberName(localUserId))) {
highlights.insert(e);
}
}
@@ -360,7 +345,7 @@ void NeoChatRoom::onAddHistoricalTimelineEvents(rev_iter_t from)
void NeoChatRoom::onRedaction(const RoomEvent &prevEvent, const RoomEvent & /*after*/)
{
if (const auto &e = eventCast<const ReactionEvent>(&prevEvent)) {
if (auto relatedEventId = e->relation().eventId; !relatedEventId.isEmpty()) {
if (auto relatedEventId = e->eventId(); !relatedEventId.isEmpty()) {
Q_EMIT updatedEvent(relatedEventId);
}
}
@@ -368,9 +353,8 @@ void NeoChatRoom::onRedaction(const RoomEvent &prevEvent, const RoomEvent & /*af
void NeoChatRoom::countChanged()
{
if (displayed() && !hasUnreadMessages()) {
resetNotificationCount();
resetHighlightCount();
if (displayed() && unreadStats().empty()) {
setReadReceipt(lastEvent()->id());
}
}
@@ -443,11 +427,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
const bool prettyPrint = (format == Qt::RichText);
using namespace Quotient;
#ifdef QUOTIENT_07
return switchOnType(
#else
return visit(
#endif
evt,
[this, format, stripNewlines](const RoomMessageEvent &e) {
using namespace MessageEventContent;
@@ -493,14 +473,9 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
[this, prettyPrint](const RoomMemberEvent &e) {
// FIXME: Rewind to the name that was at the time of this event
auto subjectName = this->htmlSafeMemberName(e.userId());
if (e.membership() == MembershipType::Leave) {
#ifdef QUOTIENT_07
if (e.membership() == Membership::Leave) {
if (e.prevContent() && e.prevContent()->displayName) {
subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped();
#else
if (e.prevContent() && e.prevContent()->displayName.isEmpty()) {
subjectName = sanitized(e.prevContent()->displayName).toHtmlEscaped();
#endif
}
}
@@ -511,7 +486,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
// The below code assumes senderName output in AuthorRole
switch (e.membership()) {
case MembershipType::Invite:
case Membership::Invite:
if (e.repeatsState()) {
auto text = i18n("reinvited %1 to the room", subjectName);
if (!e.reason().isEmpty()) {
@@ -520,13 +495,13 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
return text;
}
Q_FALLTHROUGH();
case MembershipType::Join: {
case Membership::Join: {
QString text{};
// Part 1: invites and joins
if (e.repeatsState()) {
text = i18n("joined the room (repeated)");
} else if (e.changesMembership()) {
text = e.membership() == MembershipType::Invite ? i18n("invited %1 to the room", subjectName) : i18n("joined the room");
text = e.membership() == Membership::Invite ? i18n("invited %1 to the room", subjectName) : i18n("joined the room");
}
if (!text.isEmpty()) {
if (!e.reason().isEmpty()) {
@@ -536,23 +511,19 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
}
// Part 2: profile changes of joined members
if (e.isRename()) {
if (e.displayName().isEmpty()) {
if (e.newDisplayName()) {
text = i18nc("their refers to a singular user", "cleared their display name");
} else {
text = i18nc("their refers to a singular user", "changed their display name to %1", e.displayName().toHtmlEscaped());
text = i18nc("their refers to a singular user", "changed their display name to %1", e.newDisplayName()->toHtmlEscaped());
}
}
if (e.isAvatarUpdate()) {
if (!text.isEmpty()) {
text += i18n(" and ");
}
if (e.avatarUrl().isEmpty()) {
if (e.newAvatarUrl()) {
text += i18nc("their refers to a singular user", "cleared their avatar");
#ifdef QUOTIENT_07
} else if (!e.prevContent()->avatarUrl) {
#else
} else if (e.prevContent()->avatarUrl.isEmpty()) {
#endif
text += i18n("set an avatar");
} else {
text += i18nc("their refers to a singular user", "updated their avatar");
@@ -563,18 +534,18 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
}
return text;
}
case MembershipType::Leave:
if (e.prevContent() && e.prevContent()->membership == MembershipType::Invite) {
case Membership::Leave:
if (e.prevContent() && e.prevContent()->membership == Membership::Invite) {
return (e.senderId() != e.userId()) ? i18n("withdrew %1's invitation", subjectName) : i18n("rejected the invitation");
}
if (e.prevContent() && e.prevContent()->membership == MembershipType::Ban) {
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
return (e.senderId() != e.userId()) ? i18n("unbanned %1", subjectName) : i18n("self-unbanned");
}
return (e.senderId() != e.userId())
? i18n("has put %1 out of the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
: i18n("left the room");
case MembershipType::Ban:
case Membership::Ban:
if (e.senderId() != e.userId()) {
if (e.reason().isEmpty()) {
return i18n("banned %1 from the room", subjectName);
@@ -584,7 +555,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
} else {
return i18n("self-banned from the room");
}
case MembershipType::Knock: {
case Membership::Knock: {
QString reason(e.contentJson()["reason"_ls].toString().toHtmlEscaped());
return reason.isEmpty() ? i18n("requested an invite") : i18n("requested an invite with reason: %1", reason);
}
@@ -618,7 +589,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
[](const RoomPowerLevelsEvent &) {
return i18nc("'power level' means permission level", "changed the power levels for this room");
},
[](const StateEventBase &e) {
[](const StateEvent &e) {
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
return i18n("changed the server access control lists for this room");
}
@@ -634,21 +605,15 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
},
#ifdef QUOTIENT_07
[](const PollStartEvent &e) {
return e.question();
},
#endif
i18n("Unknown event"));
}
QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
{
#ifdef QUOTIENT_07
return switchOnType(
#else
return visit(
#endif
evt,
[](const RoomMessageEvent &e) {
Q_UNUSED(e)
@@ -660,25 +625,25 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
},
[](const RoomMemberEvent &e) {
switch (e.membership()) {
case MembershipType::Invite:
case Membership::Invite:
if (e.repeatsState()) {
return i18n("reinvited someone to the room");
}
Q_FALLTHROUGH();
case MembershipType::Join: {
case Membership::Join: {
QString text{};
// Part 1: invites and joins
if (e.repeatsState()) {
text = i18n("joined the room (repeated)");
} else if (e.changesMembership()) {
text = e.membership() == MembershipType::Invite ? i18n("invited someone to the room") : i18n("joined the room");
text = e.membership() == Membership::Invite ? i18n("invited someone to the room") : i18n("joined the room");
}
if (!text.isEmpty()) {
return text;
}
// Part 2: profile changes of joined members
if (e.isRename()) {
if (e.displayName().isEmpty()) {
if (e.newDisplayName()) {
text = i18nc("their refers to a singular user", "cleared their display name");
} else {
text = i18nc("their refers to a singular user", "changed their display name");
@@ -688,13 +653,9 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
if (!text.isEmpty()) {
text += i18n(" and ");
}
if (e.avatarUrl().isEmpty()) {
if (e.newAvatarUrl()) {
text += i18nc("their refers to a singular user", "cleared their avatar");
#ifdef QUOTIENT_07
} else if (!e.prevContent()->avatarUrl) {
#else
} else if (e.prevContent()->avatarUrl.isEmpty()) {
#endif
text += i18n("set an avatar");
} else {
text += i18nc("their refers to a singular user", "updated their avatar");
@@ -705,22 +666,22 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
}
return text;
}
case MembershipType::Leave:
if (e.prevContent() && e.prevContent()->membership == MembershipType::Invite) {
case Membership::Leave:
if (e.prevContent() && e.prevContent()->membership == Membership::Invite) {
return (e.senderId() != e.userId()) ? i18n("withdrew a user's invitation") : i18n("rejected the invitation");
}
if (e.prevContent() && e.prevContent()->membership == MembershipType::Ban) {
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
return (e.senderId() != e.userId()) ? i18n("unbanned a user") : i18n("self-unbanned");
}
return (e.senderId() != e.userId()) ? i18n("put a user out of the room") : i18n("left the room");
case MembershipType::Ban:
case Membership::Ban:
if (e.senderId() != e.userId()) {
return i18n("banned a user from the room");
} else {
return i18n("self-banned from the room");
}
case MembershipType::Knock: {
case Membership::Knock: {
return i18n("requested an invite");
}
default:;
@@ -748,7 +709,7 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
[](const RoomPowerLevelsEvent &) {
return i18nc("'power level' means permission level", "changed the power levels for this room");
},
[](const StateEventBase &e) {
[](const StateEvent &e) {
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
return i18n("changed the server access control lists for this room");
}
@@ -763,28 +724,19 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
}
return i18n("updated the state");
},
#ifdef QUOTIENT_07
[](const PollStartEvent &e) {
Q_UNUSED(e);
return i18n("started a poll");
},
#endif
i18n("Unknown event"));
}
void NeoChatRoom::changeAvatar(const QUrl &localFile)
{
const auto job = connection()->uploadFile(localFile.toLocalFile());
#ifdef QUOTIENT_07
if (isJobPending(job)) {
#else
if (isJobRunning(job)) {
#endif
connect(job, &BaseJob::success, this, [this, job] {
#ifdef QUOTIENT_07
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", QString(), QJsonObject{{"url", job->contentUri().toString()}});
#else
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", QString(), QJsonObject{{"url", job->contentUri()}});
#endif
});
}
}
@@ -893,11 +845,11 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction
QStringList redactEventIds; // What if there are multiple reaction events?
const auto &annotations = relatedEvents(evt, EventRelation::Annotation());
const auto &annotations = relatedEvents(evt, EventRelation::AnnotationType);
if (!annotations.isEmpty()) {
for (const auto &a : annotations) {
if (auto e = eventCast<const ReactionEvent>(a)) {
if (e->relation().key != reaction) {
if (e->key() != reaction) {
continue;
}
@@ -920,18 +872,15 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction
bool NeoChatRoom::containsUser(const QString &userID) const
{
auto u = Room::user(userID);
if (!u) {
return false;
}
return Room::memberJoinState(u) != JoinState::Leave;
return !isMember(userID);
}
bool NeoChatRoom::canSendEvent(const QString &eventType) const
{
auto plEvent = getCurrentState<RoomPowerLevelsEvent>();
auto plEvent = currentState().get<RoomPowerLevelsEvent>();
if (!plEvent) {
return true;
}
auto pl = plEvent->powerLevelForEvent(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
@@ -940,28 +889,19 @@ bool NeoChatRoom::canSendEvent(const QString &eventType) const
bool NeoChatRoom::canSendState(const QString &eventType) const
{
auto plEvent = getCurrentState<RoomPowerLevelsEvent>();
auto plEvent = currentState().get<RoomPowerLevelsEvent>();
if (!plEvent) {
return false;
}
auto pl = plEvent->powerLevelForState(eventType);
auto currentPl = plEvent->powerLevelForUser(localUser()->id());
#ifndef QUOTIENT_07
if (eventType == "m.room.history_visibility" || eventType == "org.matrix.room.preview_urls") {
return false;
} else {
return currentPl >= pl;
}
#else
return currentPl >= pl;
#endif
}
bool NeoChatRoom::readMarkerLoaded() const
{
#ifdef QUOTIENT_07
const auto it = findInTimeline(lastFullyReadEventId());
#else
const auto it = findInTimeline(readMarkerEventId());
#endif
return it != historyEdge();
}
@@ -972,7 +912,7 @@ bool NeoChatRoom::isInvite() const
bool NeoChatRoom::isUserBanned(const QString &user) const
{
return getCurrentState<RoomMemberEvent>(user)->membership() == MembershipType::Ban;
return currentState().get<RoomMemberEvent>(user)->membership() == Membership::Ban;
}
QString NeoChatRoom::htmlSafeDisplayName() const
@@ -987,7 +927,7 @@ void NeoChatRoom::deleteMessagesByUser(const QString &user, const QString &reaso
QString NeoChatRoom::joinRule() const
{
return getCurrentState<JoinRulesEvent>()->joinRule();
return currentState().get<JoinRulesEvent>()->joinRule();
}
void NeoChatRoom::setJoinRule(const QString &joinRule)
@@ -996,21 +936,13 @@ void NeoChatRoom::setJoinRule(const QString &joinRule)
qWarning() << "Power level too low to set join rules";
return;
}
#ifdef QUOTIENT_07
setState("m.room.join_rules", "", QJsonObject{{"join_rule", joinRule}});
#else
setState<JoinRulesEvent>(QJsonObject{{"type", "m.room.join_rules"}, {"state_key", ""}, {"content", QJsonObject{{"join_rule", joinRule}}}});
#endif
// Not emitting joinRuleChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
}
QString NeoChatRoom::historyVisibility() const
{
#ifdef QUOTIENT_07
return currentState().get("m.room.history_visibility")->contentJson()["history_visibility"_ls].toString();
#else
return getCurrentState("m.room.history_visibility")->contentJson()["history_visibility"_ls].toString();
#endif
}
void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
@@ -1020,23 +952,14 @@ void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
return;
}
#ifdef QUOTIENT_07
setState("m.room.history_visibility", "", QJsonObject{{"history_visibility", historyVisibilityRule}});
#else
qWarning() << "Quotient 0.7 required to set history visibility";
return;
#endif
// Not emitting historyVisibilityChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
}
bool NeoChatRoom::defaultUrlPreviewState() const
{
#ifdef QUOTIENT_07
auto urlPreviewsDisabled = currentState().get("org.matrix.room.preview_urls");
#else
auto urlPreviewsDisabled = getCurrentState("org.matrix.room.preview_urls");
#endif
// Some rooms will not have this state event set so check for a nullptr return.
if (urlPreviewsDisabled != nullptr) {
@@ -1082,12 +1005,7 @@ void NeoChatRoom::setDefaultUrlPreviewState(const bool &defaultUrlPreviewState)
*
* You just have to set disable to true to disable URL previews by default.
*/
#ifdef QUOTIENT_07
setState("org.matrix.room.preview_urls", "", QJsonObject{{"disable", !defaultUrlPreviewState}});
#else
qWarning() << "Quotient 0.7 required to set room default url preview setting";
return;
#endif
}
bool NeoChatRoom::urlPreviewEnabled() const
@@ -1125,32 +1043,20 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
qWarning() << "Power level too low to set user power levels";
return;
}
#ifdef QUOTIENT_07
if (!isMember(userID)) {
#else
if (memberJoinState(user(userID)) == JoinState::Join) {
#endif
qWarning() << "User is not a member of this room so power level cannot be set";
return;
}
int clampPowerLevel = std::clamp(powerLevel, 0, 100);
#ifdef QUOTIENT_07
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
#else
auto powerLevelContent = getCurrentState<RoomPowerLevelsEvent>()->contentJson();
#endif
auto powerLevelUserOverrides = powerLevelContent["users"].toObject();
if (powerLevelUserOverrides[userID] != clampPowerLevel) {
powerLevelUserOverrides[userID] = clampPowerLevel;
powerLevelContent["users"] = powerLevelUserOverrides;
#ifdef QUOTIENT_07
setState("m.room.power_levels", "", powerLevelContent);
#else
setState<RoomPowerLevelsEvent>(QJsonObject{{"type", "m.room.power_levels"}, {"state_key", ""}, {"content", powerLevelContent}});
#endif
}
}
@@ -1162,11 +1068,7 @@ int NeoChatRoom::getUserPowerLevel(const QString &userId) const
int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const
{
#ifdef QUOTIENT_07
const auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
#else
const auto powerLevelEvent = getCurrentState<RoomPowerLevelsEvent>();
#endif
if (eventName == "ban") {
return powerLevelEvent->ban();
} else if (eventName == "kick") {
@@ -1190,11 +1092,7 @@ int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent)
void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent)
{
#ifdef QUOTIENT_07
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
#else
auto powerLevelContent = getCurrentState<RoomPowerLevelsEvent>()->contentJson();
#endif
int clampPowerLevel = std::clamp(newPowerLevel, 0, 100);
int powerLevel = 0;
@@ -1223,11 +1121,7 @@ void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLev
}
}
#ifdef QUOTIENT_07
setState("m.room.power_levels", "", powerLevelContent);
#else
setState<RoomPowerLevelsEvent>(QJsonObject{{"type", "m.room.power_levels"}, {"state_key", ""}, {"content", powerLevelContent}});
#endif
}
int NeoChatRoom::defaultUserPowerLevel() const
@@ -1450,11 +1344,7 @@ bool NeoChatRoom::isSpace()
return false;
}
#ifdef QUOTIENT_07
return creationEvent->roomType() == RoomType::Space;
#else
return false;
#endif
}
PushNotificationState::State NeoChatRoom::pushNotificationState() const
@@ -1781,15 +1671,9 @@ void NeoChatRoom::setSavedText(const QString &savedText)
bool NeoChatRoom::canEncryptRoom() const
{
#ifdef QUOTIENT_07
#ifdef Quotient_E2EE_ENABLED
return !usesEncryption() && canSendState("m.room.encryption");
#endif
#endif
return false;
}
#ifdef QUOTIENT_07
PollHandler *NeoChatRoom::poll(const QString &eventId)
{
if (!m_polls.contains(eventId)) {
@@ -1800,7 +1684,6 @@ PollHandler *NeoChatRoom::poll(const QString &eventId)
}
return m_polls[eventId];
}
#endif
bool NeoChatRoom::downloadTempFile(const QString &eventId)
{

View File

@@ -735,7 +735,6 @@ public:
*/
void setSavedText(const QString &savedText);
#ifdef QUOTIENT_07
/**
* @brief Get a PollHandler object for the given event Id.
*
@@ -747,7 +746,6 @@ public:
* @sa PollHandler
*/
Q_INVOKABLE PollHandler *poll(const QString &eventId);
#endif
private:
QSet<const Quotient::RoomEvent *> highlights;
@@ -775,9 +773,7 @@ private:
QVector<Mention> m_mentions;
QVector<Mention> m_editMentions;
QString m_savedText;
#ifdef QUOTIENT_07
QCache<QString, PollHandler> m_polls;
#endif
private Q_SLOTS:
void countChanged();

View File

@@ -11,11 +11,7 @@
#include <KNotification>
#include <KNotificationReplyAction>
#ifdef QUOTIENT_07
#include <accountregistry.h>
#else
#include "neochataccountregistry.h"
#endif
#include <connection.h>
#include <csapi/pushrules.h>
@@ -72,11 +68,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
connect(notification, &KNotification::defaultActivated, this, [=]() {
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
if (room->localUser()->id() != Controller::instance().activeConnection()->userId()) {
#ifdef QUOTIENT_07
Controller::instance().setActiveConnection(Accounts.get(room->localUser()->id()));
#else
Controller::instance().setActiveConnection(AccountRegistry::instance().get(room->localUser()->id()));
#endif
}
RoomManager::instance().enterRoom(room);
});

View File

@@ -34,7 +34,7 @@ void PollHandler::setRoom(NeoChatRoom *room)
connect(room, &NeoChatRoom::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
for (const auto &event : events) {
if (event->is<PollEndEvent>()) {
auto pl = m_room->getCurrentState<RoomPowerLevelsEvent>();
auto pl = m_room->currentState().get<RoomPowerLevelsEvent>();
auto userPl = pl->powerLevelForUser(event->senderId());
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= pl->redact()) {
m_hasEnded = true;
@@ -75,7 +75,7 @@ void PollHandler::checkLoadRelations()
connect(job, &BaseJob::success, this, [this, job]() {
for (const auto &event : job->chunk()) {
if (event->is<PollEndEvent>()) {
auto pl = m_room->getCurrentState<RoomPowerLevelsEvent>();
auto pl = m_room->currentState().get<RoomPowerLevelsEvent>();
auto userPl = pl->powerLevelForUser(event->senderId());
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= pl->redact()) {
m_hasEnded = true;

View File

@@ -35,7 +35,7 @@ QQC2.Dialog {
text: i18n("Sign out")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
onClicked: {
Controller.logout(Controller.activeConnection, true);
Controller.activeConnection.logout();
root.close();
}
}

View File

@@ -40,12 +40,12 @@ Labs.MenuBar {
Labs.MenuItem {
text: i18nc("menu", "New Private Chat…")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.activeConnection
onTriggered: pushReplaceLayer("qrc:/StartChatPage.qml", {connection: Controller.activeConnection})
}
Labs.MenuItem {
text: i18nc("menu", "New Group…")
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.accountCount > 0
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && Controller.activeConnection
shortcut: StandardKey.New
onTriggered: {
const dialog = createRoomDialog.createObject(root.overlay)

View File

@@ -76,7 +76,7 @@ QQC2.ToolBar {
visible: switchUserButton.checked
onVisibleChanged: if (visible) accounts.forceActiveFocus()
clip: true
model: AccountRegistry
model: Accounts
keyNavigationEnabled: false
Keys.onDownPressed: {

View File

@@ -0,0 +1,502 @@
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick 2.15
import QtQuick.Controls 2.15 as QQC2
import QtQuick.Layouts 1.15
import QtQml.Models 2.15
import org.kde.kirigami 2.15 as Kirigami
import org.kde.kitemmodels 1.0
import org.kde.neochat 1.0
Kirigami.ScrollablePage {
id: page
/**
* @brief The current width of the room list.
*
* @note Other objects can access the value but the private function makes sure
* that only the internal members can modify it.
*/
readonly property int currentWidth: _private.currentWidth
readonly property bool collapsed: Config.collapsed
onCollapsedChanged: if (collapsed) {
sortFilterRoomListModel.filterText = "";
}
header: ColumnLayout {
visible: !page.collapsed
spacing: 0
ListView {
id: spaceList
property string activeSpaceId: ""
orientation: Qt.Horizontal
spacing: Kirigami.Units.smallSpacing
clip: true
visible: spaceList.count > 0
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
Layout.fillWidth: true
model: SortFilterSpaceListModel {
id: sortFilterSpaceListModel
sourceModel: roomListModel
}
header: QQC2.ItemDelegate {
id: homeButton
icon.name: "home"
text: i18nc("@action:button", "Show All Rooms")
height: parent.height
width: height
leftPadding: topPadding
rightPadding: topPadding
contentItem: Kirigami.Icon {
source: "home"
}
onClicked: {
sortFilterRoomListModel.activeSpaceId = "";
spaceList.activeSpaceId = '';
listView.positionViewAtIndex(0, ListView.Beginning);
}
QQC2.ToolTip.text: homeButton.text
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
delegate: QQC2.ItemDelegate {
required property string avatar
required property var currentRoom
required property int index
required property string id
height: parent.height
width: height
leftPadding: topPadding
rightPadding: topPadding
contentItem: Kirigami.Avatar {
name: currentRoom.displayName
source: avatar !== "" ? "image://mxc/" + avatar : ""
}
onClicked: {
spaceList.activeSpaceId = id;
sortFilterRoomListModel.activeSpaceId = id;
}
Accessible.name: currentRoom.displayName
QQC2.ToolTip.text: currentRoom.displayName
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onPressAndHold: {
spaceList.createContextMenu(currentRoom)
}
TapHandler {
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse
onTapped: spaceList.createContextMenu(currentRoom)
}
}
function createContextMenu(room) {
const menu = spaceListContextMenu.createObject(page, {room: room})
menu.open()
}
}
Kirigami.Separator {
Layout.fillWidth: true
}
Component {
id: spaceListContextMenu
SpaceListContextMenu {}
}
}
property var enteredRoom
Connections {
target: RoomManager
function onCurrentRoomChanged() {
itemSelection.setCurrentIndex(roomListModel.index(roomListModel.indexForRoom(RoomManager.currentRoom), 0), ItemSelectionModel.SelectCurrent)
}
}
function goToNextRoomFiltered(condition) {
let index = listView.currentIndex;
while (index++ !== listView.count - 1) {
if (condition(listView.itemAtIndex(index))) {
listView.currentIndex = index;
listView.currentItem.action.trigger();
return;
}
}
}
function goToPreviousRoomFiltered(condition) {
let index = listView.currentIndex;
while (index-- !== 0) {
if (condition(listView.itemAtIndex(index))) {
listView.currentIndex = index;
listView.currentItem.action.trigger();
return;
}
}
}
function goToNextRoom() {
goToNextRoomFiltered((item) => item.visible);
}
function goToPreviousRoom() {
goToPreviousRoomFiltered((item) => item.visible);
}
function goToNextUnreadRoom() {
goToNextRoomFiltered((item) => (item.visible && item.hasUnread));
}
function goToPreviousUnreadRoom() {
goToPreviousRoomFiltered((item) => (item.visible && item.hasUnread));
}
titleDelegate: ExploreComponent {
Layout.fillWidth: true
desiredWidth: page.width - Kirigami.Units.largeSpacing
collapsed: page.collapsed
}
ListView {
id: listView
activeFocusOnTab: true
clip: Accounts.count > 1
header: QQC2.ItemDelegate {
visible: page.collapsed
action: Kirigami.Action {
id: enterRoomAction
onTriggered: quickView.item.open();
}
topPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
width: visible ? ListView.view.width : 0
height: visible ? Kirigami.Units.gridUnit * 2 : 0
Kirigami.Icon {
anchors.centerIn: parent
width: 22
height: 22
source: "search"
}
Kirigami.Separator {
width: parent.width
anchors.bottom: parent.bottom
}
}
Layout.fillWidth: true
Kirigami.PlaceholderMessage {
anchors.centerIn: parent
width: parent.width - (Kirigami.Units.largeSpacing * 4)
visible: listView.count == 0
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("No rooms found") : i18n("Join some rooms to get started")
helpfulAction: Kirigami.Action {
icon.name: sortFilterRoomListModel.filterText.length > 0 ? "search" : "list-add"
text: sortFilterRoomListModel.filterText.length > 0 ? i18n("Search in room directory") : i18n("Explore rooms")
onTriggered: pageStack.layers.push("qrc:/JoinRoomPage.qml", {
connection: Controller.activeConnection,
keyword: sortFilterRoomListModel.filterText
})
}
}
ItemSelectionModel {
id: itemSelection
model: roomListModel
onCurrentChanged: {
listView.currentIndex = sortFilterRoomListModel.mapFromSource(current).row
}
}
model: SortFilterRoomListModel {
id: sortFilterRoomListModel
sourceModel: RoomListModel {
id: roomListModel
connection: Controller.activeConnection
}
roomSortOrder: Config.mergeRoomList ? SortFilterRoomListModel.LastActivity : SortFilterRoomListModel.Categories
onLayoutChanged: {
listView.currentIndex = sortFilterRoomListModel.mapFromSource(itemSelection.currentIndex).row
}
}
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
section.delegate: page.collapsed ? foldButton : sectionHeader
Component {
id: sectionHeader
Kirigami.ListSectionHeader {
height: implicitHeight
label: roomListModel.categoryName(section)
action: Kirigami.Action {
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
}
contentItem.children: QQC2.ToolButton {
icon.name: (roomListModel.categoryVisible(section) ? "go-up" : "go-down")
icon.width: Kirigami.Units.iconSizes.small
icon.height: Kirigami.Units.iconSizes.small
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
}
}
}
Component {
id: foldButton
Item {
width: ListView.view.width
height: visible ? width : 0
QQC2.ToolButton {
id: button
anchors.centerIn: parent
icon.name: hovered ? (roomListModel.categoryVisible(section) ? "go-up" : "go-down") : roomListModel.categoryIconName(section)
icon.width: Kirigami.Units.iconSizes.smallMedium
icon.height: Kirigami.Units.iconSizes.smallMedium
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
QQC2.ToolTip.text: roomListModel.categoryName(section)
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
}
reuseItems: true
currentIndex: -1 // we don't want any room highlighted by default
delegate: page.collapsed ? collapsedModeListComponent : normalModeListComponent
Component {
id: collapsedModeListComponent
QQC2.ItemDelegate {
action: Kirigami.Action {
id: enterRoomAction
onTriggered: {
RoomManager.enterRoom(currentRoom);
}
}
Keys.onEnterPressed: enterRoomAction.trigger()
Keys.onReturnPressed: enterRoomAction.trigger()
topPadding: Kirigami.Units.largeSpacing
leftPadding: Kirigami.Units.largeSpacing
rightPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
width: ListView.view.width
height: visible ? ListView.view.width : 0
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
contentItem: Kirigami.Avatar {
source: avatar ? "image://mxc/" + avatar : ""
name: model.name || i18n("No Name")
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
}
QQC2.ToolTip {
enabled: text.length !== 0
text: name ?? ""
}
}
}
Component {
id: roomListContextMenu
RoomListContextMenu {}
}
Component {
id: normalModeListComponent
Kirigami.BasicListItem {
id: roomListItem
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
topPadding: Kirigami.Units.largeSpacing
bottomPadding: Kirigami.Units.largeSpacing
highlighted: listView.currentIndex === index
focus: true
icon: undefined
action: Kirigami.Action {
id: enterRoomAction
onTriggered: {
RoomManager.enterRoom(currentRoom);
}
}
Keys.onEnterPressed: enterRoomAction.trigger()
Keys.onReturnPressed: enterRoomAction.trigger()
bold: unreadCount > 0
label: name ?? ""
labelItem.textFormat: Text.PlainText
subtitle: subtitleText
subtitleItem.textFormat: Text.PlainText
subtitleItem.visible: !Config.compactRoomList
onPressAndHold: {
createRoomListContextMenu()
}
TapHandler {
acceptedButtons: Qt.RightButton
acceptedDevices: PointerDevice.Mouse
onTapped: createRoomListContextMenu()
}
leading: Kirigami.Avatar {
source: avatar ? "image://mxc/" + avatar : ""
name: model.name || i18n("No Name")
implicitWidth: visible ? height : 0
visible: Config.showAvatarInRoomDrawer
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
}
trailing: RowLayout {
Kirigami.Icon {
source: "notifications-disabled"
enabled: false
implicitWidth: Kirigami.Units.iconSizes.smallMedium
implicitHeight: Kirigami.Units.iconSizes.smallMedium
Layout.rightMargin: Kirigami.Units.smallSpacing
visible: currentRoom.pushNotificationState === PushNotificationState.Mute && !configButton.visible && unreadCount <= 0
Accessible.name: i18n("Muted room")
}
QQC2.Label {
id: notificationCountLabel
text: notificationCount > 0 ? notificationCount : "●"
visible: unreadCount > 0
color: Kirigami.Theme.textColor
Layout.rightMargin: Kirigami.Units.smallSpacing
Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium
Layout.minimumWidth: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height)
horizontalAlignment: Text.AlignHCenter
background: Rectangle {
visible: notificationCount > 0
Kirigami.Theme.colorSet: Kirigami.Theme.Button
color: highlightCount > 0 ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.disabledTextColor
opacity: highlightCount > 0 ? 1 : 0.3
radius: height / 2
}
TextMetrics {
id: notificationCountTextMetrics
text: notificationCountLabel.text
}
}
QQC2.Button {
id: configButton
visible: roomListItem.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList
Accessible.name: i18n("Configure room")
action: Kirigami.Action {
id: optionAction
icon.name: "configure"
onTriggered: {
createRoomListContextMenu()
}
}
}
}
function createRoomListContextMenu() {
const menu = roomListContextMenu.createObject(page, {room: currentRoom})
if (!Kirigami.Settings.isMobile && !Config.compactRoomList) {
configButton.visible = true
configButton.down = true
}
menu.closed.connect(function() {
configButton.down = undefined
configButton.visible = Qt.binding(function() { return roomListItem.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList })
})
menu.open()
}
readonly property bool hasUnread: unreadCount > 0
}
}
}
footer: UserInfo {
width: parent.width
visible: !page.collapsed
}
MouseArea {
anchors.top: parent.top
anchors.bottom: parent.bottom
parent: applicationWindow().overlay.parent
x: page.currentWidth - width / 2
width: Kirigami.Units.smallSpacing * 2
z: page.z + 1
enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
visible: enabled
cursorShape: Qt.SplitHCursor
property int _lastX
onPressed: mouse => {
_lastX = mouse.x;
}
onPositionChanged: mouse => {
if (_lastX == -1) {
return;
}
if (mouse.x > _lastX) {
// we moved to the right
if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) {
// Here we get back directly to a more wide mode.
_private.currentWidth = _private.collapseWidth;
Config.collapsed = false;
} else if (_private.currentWidth >= _private.collapseWidth) {
// Increase page width
_private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX));
}
} else if (mouse.x < _lastX) {
const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
if (tmpWidth < _private.collapseWidth) {
_private.currentWidth = Qt.binding(() => _private.collapsedSize + (page.contentItem.QQC2.ScrollBar.vertical.visible ? page.contentItem.QQC2.ScrollBar.vertical.width : 0));
Config.collapsed = true;
} else {
_private.currentWidth = tmpWidth;
}
}
}
}
/*
* Hold the modifiable currentWidth in a private object so that only internal
* members can modify it.
*/
QtObject {
id: _private
property int currentWidth: Config.collapsed ? collapsedSize : defaultWidth
readonly property int defaultWidth: Kirigami.Units.gridUnit * 17
readonly property int collapseWidth: Kirigami.Units.gridUnit * 10
readonly property int collapsedSize: Kirigami.Units.gridUnit * 3 - Kirigami.Units.smallSpacing * 3
}
}

View File

@@ -27,7 +27,7 @@ Kirigami.ScrollablePage {
}
Repeater {
model: AccountRegistry
model: Accounts
delegate: MobileForm.AbstractFormDelegate {
Layout.fillWidth: true
onClicked: pageSettingStack.pushDialogLayer("qrc:/AccountEditorPage.qml", {

View File

@@ -231,24 +231,12 @@ Kirigami.ApplicationWindow {
}
}
Connections {
target: AccountRegistry
function onRowsRemoved() {
if (AccountRegistry.rowCount() === 0) {
RoomManager.reset();
pageStack.clear();
roomListLoaded = false;
pageStack.push("qrc:/WelcomePage.qml");
}
}
}
Connections {
target: Controller
function onInitiated() {
if (Controller.accountCount === 0) {
pageStack.replace("qrc:/WelcomePage.qml", {});
function onActiveConnectionChanged() {
if (!Controller.activeConnection) {
pageStack.replace("qrc:/imports/NeoChat/Page/WelcomePage.qml", {});
} else if (!roomListLoaded) {
pageStack.replace(roomListComponent, {
activeConnection: Controller.activeConnection

View File

@@ -11,9 +11,7 @@
#include <QQuickTextDocument>
#include <QStandardPaths>
#include <csapi/joining.h>
#ifdef QUOTIENT_07
#include <csapi/knocking.h>
#endif
#include <qt_connection_util.h>
#include <user.h>
@@ -158,16 +156,12 @@ UriResolveResult RoomManager::visitUser(User *user, const QString &action)
{
if (action == "mention" || action.isEmpty()) {
// send it has QVariantMap because the properties in the
#ifdef QUOTIENT_07
user->load();
#endif
Q_EMIT showUserDetail(user);
} else if (action == "_interactive") {
user->requestDirectChat();
} else if (action == "chat") {
#ifdef QUOTIENT_07
user->load();
#endif
Q_EMIT askDirectChatConfirmation(user);
} else {
return Quotient::IncorrectAction;
@@ -210,8 +204,6 @@ void RoomManager::joinRoom(Quotient::Connection *account, const QString &roomAli
});
}
// TODO: maybe need use the function upstream later
#ifdef QUOTIENT_07
void RoomManager::knockRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QString &reason, const QStringList &viaServers)
{
auto *const job = account->callApi<KnockRoomJob>(roomAliasOrId, viaServers, reason);
@@ -229,7 +221,6 @@ void RoomManager::knockRoom(Quotient::Connection *account, const QString &roomAl
}
});
}
#endif
bool RoomManager::visitNonMatrix(const QUrl &url)
{

View File

@@ -59,10 +59,7 @@ public:
// Overrided methods from UriResolverBase
UriResolveResult visitUser(User *user, const QString &action) override;
void joinRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QStringList &viaServers) override;
// TODO: it need also override in the feature?
#ifdef QUOTIENT_07
void knockRoom(Quotient::Connection *account, const QString &roomAliasOrId, const QString &reason, const QStringList &viaServers);
#endif
Q_INVOKABLE void visitRoom(Quotient::Room *room, const QString &eventId) override;
Q_INVOKABLE bool visitNonMatrix(const QUrl &url) override;

View File

@@ -3,9 +3,7 @@
#include "spacehierarchycache.h"
#ifdef QUOTIENT_07
#include <csapi/space_hierarchy.h>
#endif
#include <qt_connection_util.h>
#include "controller.h"
@@ -26,7 +24,6 @@ SpaceHierarchyCache::SpaceHierarchyCache(QObject *parent)
void SpaceHierarchyCache::cacheSpaceHierarchy()
{
#ifdef QUOTIENT_07
auto connection = Controller::instance().activeConnection();
if (!connection) {
return;
@@ -45,7 +42,6 @@ void SpaceHierarchyCache::cacheSpaceHierarchy()
});
}
}
#endif
}
void SpaceHierarchyCache::populateSpaceHierarchy(const QString &spaceId)
@@ -54,7 +50,6 @@ void SpaceHierarchyCache::populateSpaceHierarchy(const QString &spaceId)
if (!connection) {
return;
}
#ifdef QUOTIENT_07
GetSpaceHierarchyJob *job = connection->callApi<GetSpaceHierarchyJob>(spaceId);
connect(job, &BaseJob::success, this, [this, job, spaceId]() {
@@ -69,7 +64,6 @@ void SpaceHierarchyCache::populateSpaceHierarchy(const QString &spaceId)
m_spaceHierarchy.insert(spaceId, roomList);
Q_EMIT spaceHierarchyChanged();
});
#endif
}
void SpaceHierarchyCache::addSpaceToHierarchy(Quotient::Room *room)

View File

@@ -1,35 +0,0 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "stickerevent.h"
using namespace Quotient;
StickerEvent::StickerEvent(const QJsonObject &obj)
#ifdef QUOTIENT_07
: RoomEvent(obj)
#else
: RoomEvent(typeId(), obj)
#endif
, m_imageContent(EventContent::ImageContent(obj["content"_ls].toObject()))
{
}
QString StickerEvent::body() const
{
return content<QString>("body"_ls);
}
const EventContent::ImageContent &StickerEvent::image() const
{
return m_imageContent;
}
QUrl StickerEvent::url() const
{
#ifdef QUOTIENT_07
return m_imageContent.url();
#else
return m_imageContent.url;
#endif
}

View File

@@ -1,43 +0,0 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <events/eventcontent.h>
#include <events/roomevent.h>
namespace Quotient
{
/// Sticker messages are specialised image messages that are displayed without
/// controls (e.g. no "download" link, or light-box view on click, as would be
/// displayed for for m.image events).
class StickerEvent : public RoomEvent
{
public:
#ifdef QUOTIENT_07
QUO_EVENT(StickerEvent, "m.sticker")
#else
DEFINE_EVENT_TYPEID("m.sticker", StickerEvent)
#endif
explicit StickerEvent(const QJsonObject &obj);
/// \brief A textual representation or associated description of the
/// sticker image.
///
/// This could be the alt text of the original image, or a message to
/// accompany and further describe the sticker.
QString body() const;
/// \brief Metadata about the image referred to in url including a
/// thumbnail representation.
const EventContent::ImageContent &image() const;
/// \brief The URL to the sticker image. This must be a valid mxc:// URI.
QUrl url() const;
private:
EventContent::ImageContent m_imageContent;
};
REGISTER_EVENT_TYPE(StickerEvent)
}