Compare commits

..

1 Commits

Author SHA1 Message Date
Nicolas Fella
945b27eca8 Add activation support to rooms runner
This is needed to raise the window when activating the runner action on Wayland
2023-09-23 19:39:03 +00:00
254 changed files with 109206 additions and 132366 deletions

View File

@@ -1,10 +0,0 @@
; SPDX-FileCopyrightText: None
; SPDX-License-Identifier: CC0-1.0
[BlueprintSettings]
kde/unreleased/kirigami-addons.version=master
kde/frameworks.version=master
kde/libs.version=master
kde/plasma.version=master
kde/unreleased.version=master
libs/qt.qtMajorVersion=6

View File

@@ -19,7 +19,6 @@
"--talk-name=org.freedesktop.Notifications",
"--talk-name=org.kde.kwalletd5",
"--talk-name=org.kde.StatusNotifierWatcher",
"--talk-name=org.freedesktop.secrets",
"--own-name=org.kde.StatusNotifierItem-2-2"
],
"modules": [

View File

@@ -2,12 +2,9 @@
# SPDX-License-Identifier: CC0-1.0
include:
- project: sysadmin/ci-utilities
file:
- /gitlab-templates/reuse-lint.yml
- /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml
- /gitlab-templates/windows-qt6.yml
- /gitlab-templates/freebsd-qt6.yml
# - /gitlab-templates/flatpak.yml
- /gitlab-templates/craft-android-qt6-apks.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/reuse-lint.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/android-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/linux-qt6.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/windows-qt6.yml
- https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/freebsd-qt6.yml
# - https://invent.kde.org/sysadmin/ci-utilities/raw/master/gitlab-templates/flatpak.yml

View File

@@ -13,10 +13,8 @@ Dependencies:
'frameworks/kitemmodels': '@latest-kf6'
'frameworks/kquickcharts': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6'
'frameworks/kcolorscheme': '@latest-kf6'
'libraries/kquickimageeditor': '@latest-kf6'
'frameworks/sonnet': '@latest-kf6'
'frameworks/prison': '@latest-kf6'
'libraries/kirigami-addons': '@latest-kf6'
'third-party/libquotient': '@latest'
'third-party/qtkeychain': '@latest'
@@ -27,10 +25,11 @@ Dependencies:
'frameworks/qqc2-desktop-style': '@latest-kf6'
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
'frameworks/kconfigwidgets': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
- 'on': ['Linux']
'require':

View File

@@ -41,11 +41,3 @@ License: CC0-1.0
Files: .flatpak-manifest.json
Copyright: 2020-2022 Tobias Fella <tobias.fella@kde.org>
License: BSD-2-Clause
Files: autotests/data/*
Copyright: none
License: CC0-1.0
Files: appiumtests/data/*
Copyright: 2023 Tobias Fella <tobias.fella@kde.org>
License: CC0-1.0

View File

@@ -7,9 +7,9 @@
cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "01")
set(RELEASE_SERVICE_VERSION_MICRO "80")
set(RELEASE_SERVICE_VERSION_MAJOR "23")
set(RELEASE_SERVICE_VERSION_MINOR "11")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
@@ -37,7 +37,6 @@ include(ECMAddAppIcon)
include(KDEGitCommitHooks)
include(ECMCheckOutboundLicense)
include(ECMQtDeclareLoggingCategory)
include(ECMAddAndroidApk)
if (NOT ANDROID)
include(KDEClangFormat)
endif()
@@ -58,15 +57,15 @@ set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels)
set_package_properties(KF6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
)
set_package_properties(KF6Kirigami PROPERTIES
TYPE REQUIRED
PURPOSE "Kirigami application UI framework"
)
set_package_properties(KF6Kirigami2 PROPERTIES
TYPE REQUIRED
PURPOSE "Kirigami application UI framework"
)
find_package(KF6KirigamiAddons 0.7.2 REQUIRED)
if(ANDROID)
@@ -77,17 +76,11 @@ if(ANDROID)
)
else()
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem StatusNotifierItem)
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
TYPE RUNTIME
)
ecm_find_qmlmodule(org.kde.syntaxhighlighting 1.0)
find_package(ICU 61.0 COMPONENTS uc)
set_package_properties(ICU PROPERTIES
TYPE REQUIRED
PURPOSE "Unicode library"
)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
@@ -118,8 +111,7 @@ set_package_properties(cmark PROPERTIES
ecm_find_qmlmodule(org.kde.kquickimageeditor 1.0)
ecm_find_qmlmodule(org.kde.kitemmodels 1.0)
ecm_find_qmlmodule(org.kde.quickcharts 1.0)
ecm_find_qmlmodule(QtLocation)
ecm_find_qmlmodule(org.kde.prison)
ecm_find_qmlmodule(QtLocation ${QTLOCATION_MODULE_QML_VERSION})
find_package(KQuickImageEditor COMPONENTS)
set_package_properties(KQuickImageEditor PROPERTIES
@@ -129,7 +121,7 @@ set_package_properties(KQuickImageEditor PROPERTIES
PURPOSE "Add image editing capability to image attachments"
)
find_package(QCoro6 0.4 COMPONENTS Core Network REQUIRED)
find_package(QCoro6 0.4 COMPONENTS Core REQUIRED)
qcoro_enable_coroutines()
@@ -139,13 +131,6 @@ set_package_properties(KF6DocTools PROPERTIES DESCRIPTION
TYPE OPTIONAL
)
find_package(KUnifiedPush QUIET)
set_package_properties(KUnifiedPush PROPERTIES
TYPE OPTIONAL
PURPOSE "Push notification support"
URL "https://invent.kde.org/libraries/kunifiedpush"
)
if(ANDROID)
find_package(Sqlite3)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)

View File

@@ -11,7 +11,6 @@ A Qt/QML based Matrix client.
<a href='https://matrix.org'><img src='https://matrix.org/docs/legacy/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
<a href='https://snapcraft.io/neochat'><img width='190px' alt='Download on the Snap Store' src='https://snapcraft.io/static/images/badges/en/snap-store-black.svg'/></a>
## Introduction
@@ -96,7 +95,7 @@ As is the case throughout the KDE ecosystem contributions are welcome from all.
## Contact
The best place to reach the maintainers is on the KDE Matrix instance in the NeoChat channel, [#neochat:kde.org](https://go.kde.org/matrix/#/#neochat:kde.org). See [Matrix](https://community.kde.org/Matrix) for more details.
The best place to reach the maintainers is on the KDE Matrix instance in the NeoChat channel, [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org).
## Acknowledgement

View File

@@ -9,9 +9,9 @@
android:versionName="${versionName}"
android:versionCode="${versionCode}"
android:installLocation="auto">
<application android:name="org.qtproject.qt.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat" android:usesCleartextTraffic="true">
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat" android:usesCleartextTraffic="true">
<activity android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name="org.qtproject.qt.android.bindings.QtActivity"
android:name="org.qtproject.qt5.android.bindings.QtActivity"
android:label="NeoChat"
android:windowSoftInputMode="adjustResize"
android:launchMode="singleTop"

View File

@@ -12,7 +12,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.1'
classpath 'com.android.tools.build:gradle:7.0.2'
}
}
@@ -35,7 +35,7 @@ android {
* The following variables:
* - androidBuildToolsVersion,
* - androidCompileSdkVersion
* - qtAndroidDir - holds the path to qt android files
* - qt5AndroidDir - holds the path to qt android files
* needed to build any Qt application
* on Android.
*
@@ -44,20 +44,17 @@ android {
* Changing them manually might break the compilation!
*******************************************************/
compileSdkVersion androidCompileSdkVersion
compileSdkVersion androidCompileSdkVersion.toInteger()
buildToolsVersion androidBuildToolsVersion
ndkVersion androidNdkVersion
// Extract native libraries from the APK
packagingOptions.jniLibs.useLegacyPackaging true
sourceSets {
main {
manifest.srcFile 'AndroidManifest.xml'
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qtAndroidDir + '/res', 'res']
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
res.srcDirs = [qt5AndroidDir + '/res', 'res']
resources.srcDirs = ['src']
renderscript.srcDirs = ['src']
assets.srcDirs = ['assets']

View File

@@ -21,8 +21,3 @@ add_test(
NAME logintest
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/logintest.py
)
add_test(
NAME openuserdetailstest
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/openuserdetailstest.py
)

View File

@@ -1,3 +0,0 @@
{
"next_batch": "batch1234"
}

View File

@@ -1,50 +0,0 @@
{
"next_batch": "batch1234",
"rooms": {
"join": {
"!room_id_1234:localhost:1234": {
"state": {
"events": [
{
"type": "m.room.member",
"state_key": "@user:localhost:1234",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_0:localhost:1234",
"room_id": "!room_id_1234:localhost:1234",
"content": {
"avatar_url": "",
"displayname": "A Display Name",
"membership": "join",
"reason": "Nothing"
},
"unsigned": {
"age": 1234
}
}
]
},
"timeline": {
"events": [
{
"type": "m.room.message",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_1:localhost:1234",
"room_id": "!room_id_1234:localhost:1234",
"content": {
"body": "This is a message",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://matrix.to/#/@user:localhost:1234\">User</a>:",
"msgtype": "m.text"
},
"unsigned": {
"age": 1234
}
}
]
}
}
}
}
}

View File

@@ -1,11 +1,9 @@
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
import json
from flask import Flask, request, abort
import os
app = Flask(__name__)
from flask import Flask, request, abort
app = Flask(__name__)
@app.route("/_matrix/client/v3/login", methods=["GET"])
def login_get():
@@ -14,13 +12,6 @@ def login_get():
result["flows"][0]["type"] = "m.login.password"
return result
@app.route("/_matrix/client/v3/account/whoami", methods=["GET"])
def whoami():
result = dict()
result["device_id"] = "device_id_1234"
result["user_id"] = "@user:localhost:1234"
return result
@app.route("/_matrix/client/v3/login", methods=["POST"])
def login_post():
data = request.get_json()
@@ -28,22 +19,15 @@ def login_post():
abort(403)
print(data)
result = dict()
result["access_token"] = "token_login"
result["access_token"] = "token_1234"
result["device_id"] = "device_1234"
result["user_id"] = "@user:localhost:1234"
return result
def load_json(name):
parts = __file__.split("/")
parts.pop()
datadir = "/".join(parts)
return json.loads(open(f"{datadir}/data/{name}.json").read())
@app.route("/_matrix/client/r0/sync")
def sync():
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
result = dict()
result["next_batch"] = "batch1234"
return result
@app.route("/.well-known/matrix/client")
@@ -53,18 +37,6 @@ def well_known():
reply["m.homeserver"]["base_url"] = "https://localhost:1234"
return reply
@app.route("/_matrix/client/v3/profile/<id>")
def profile(id):
reply = dict()
reply["avatar_url"] = "mxc://localhost:1234/asdf1234"
reply["displayname"] = "User123"
return reply
@app.route("/_matrix/client/v3/keys/upload", methods=["POST"])
def upload_keys():
reply = dict()
return reply
if __name__ == "__main__":
app.run(ssl_context='adhoc', port=1234)

View File

@@ -4,26 +4,25 @@
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
import unittest
from appium import webdriver
from appium.webdriver.common.appiumby import AppiumBy
from selenium.webdriver.support.ui import WebDriverWait
import os
import time
import subprocess
import sys
import unittest
from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
class LoginTest(unittest.TestCase):
mockServerProcess: subprocess.Popen
@classmethod
def setUpClass(cls):
options = AppiumOptions()
options.set_capability("app", "neochat --ignore-ssl-errors")
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
def setUpClass(self):
desired_caps = {}
desired_caps["app"] = "neochat --ignore-ssl-errors"
desired_caps["timeouts"] = {'implicit': 10000}
self.driver = webdriver.Remote(
command_executor='http://127.0.0.1:4723',
desired_capabilities=desired_caps)
self.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
def setUp(self):
pass
@@ -46,6 +45,5 @@ class LoginTest(unittest.TestCase):
self.driver.find_element(by=AppiumBy.NAME, value="Login").click()
self.driver.find_element(by=AppiumBy.NAME, value="Join some rooms to get started").click()
if __name__ == '__main__':
unittest.main()

View File

@@ -1,48 +0,0 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
import os
import subprocess
import sys
import unittest
from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
class OpenUserDetailsTest(unittest.TestCase):
mockServerProcess: subprocess.Popen
@classmethod
def setUpClass(cls):
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
options = AppiumOptions()
options.set_capability("app", "neochat --ignore-ssl-errors --test")
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
def setUp(self):
pass
def tearDown(self):
if not self._outcome.result.wasSuccessful():
self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id()))
@classmethod
def tearDownClass(self):
self.mockServerProcess.terminate()
self.driver.quit()
def test_open_sheet(self):
self.driver.find_element(by=AppiumBy.NAME, value="@user:localhost:1234").click()
self.driver.find_element(by=AppiumBy.NAME, value="Empty room (!room_id_1234:localhost:1234)").click()
self.driver.find_element(by=AppiumBy.NAME, value="A Display Name").click()
self.driver.find_element(by=AppiumBy.NAME, value="Account Details")
if __name__ == '__main__':
unittest.main()

View File

@@ -3,8 +3,6 @@
enable_testing()
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}/data" )
ecm_add_test(
neochatroomtest.cpp
LINK_LIBRARIES neochat Qt::Test
@@ -34,15 +32,3 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test
TEST_NAME eventhandlertest
)
ecm_add_test(
chatbarcachetest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME chatbarcachetest
)
ecm_add_test(
chatdocumenthandlertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME chatdocumenthandlertest
)

View File

@@ -1,157 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QJsonDocument>
#include <QJsonObject>
#include <QObject>
#include <QTest>
#include <Quotient/syncdata.h>
#include <qtestcase.h>
#include "chatbarcache.h"
#include "neochatroom.h"
using namespace Quotient;
class TestRoom : public NeoChatRoom
{
public:
using NeoChatRoom::NeoChatRoom;
void update(SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
};
class ChatBarCacheTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void empty();
void noRoom();
void badParent();
void reply();
void edit();
void attachment();
};
void ChatBarCacheTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testMinSyncFile;
testMinSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-min-sync.json"));
testMinSyncFile.open(QIODevice::ReadOnly);
const auto testMinSyncJson = QJsonDocument::fromJson(testMinSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testMinSyncJson.object());
room->update(std::move(roomData));
}
void ChatBarCacheTest::empty()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
QCOMPARE(chatBarCache->text(), QString());
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::noRoom()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache());
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
// These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString());
}
void ChatBarCacheTest::badParent()
{
QScopedPointer<QObject> badParent(new QObject());
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(badParent.get()));
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
// These should return empty even though a reply ID has been set because the
// ChatBarCache has no parent.
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
QCOMPARE(chatBarCache->relationMessage(), QString());
}
void ChatBarCacheTest::reply()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
chatBarCache->setReplyId(QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), true);
QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::edit()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), true);
QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
QCOMPARE(chatBarCache->relationUser(), room->getUser(room->user(QLatin1String("@example:example.org"))));
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
QCOMPARE(chatBarCache->attachmentPath(), QString());
}
void ChatBarCacheTest::attachment()
{
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
chatBarCache->setText(QLatin1String("some text"));
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
QCOMPARE(chatBarCache->isReplying(), false);
QCOMPARE(chatBarCache->replyId(), QString());
QCOMPARE(chatBarCache->isEditing(), false);
QCOMPARE(chatBarCache->editId(), QString());
QCOMPARE(chatBarCache->relationUser(), room->getUser(nullptr));
QCOMPARE(chatBarCache->relationMessage(), QString());
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
}
QTEST_MAIN(ChatBarCacheTest)
#include "chatbarcachetest.moc"

View File

@@ -1,36 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject>
#include <QTest>
#include "chatdocumenthandler.h"
#include "neochatconfig.h"
class ChatDocumentHandlerTest : public QObject
{
Q_OBJECT
private:
ChatDocumentHandler emptyHandler;
private Q_SLOTS:
void initTestCase();
void nullComplete();
};
void ChatDocumentHandlerTest::initTestCase()
{
// HACK: this is to stop KStatusNotifierItem SEGFAULTING on cleanup.
NeoChatConfig::self()->setSystemTray(false);
}
void ChatDocumentHandlerTest::nullComplete()
{
QTest::ignoreMessage(QtWarningMsg, "complete called with m_document set to nullptr.");
emptyHandler.complete(0);
}
QTEST_MAIN(ChatDocumentHandlerTest)
#include "chatdocumenthandlertest.moc"

View File

@@ -1,410 +0,0 @@
{
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
},
{
"content": {
"$153456789:example.org": {
"m.read": {
"@alice:matrix.org": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@bob:example.com": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tim:example.com": {
"ts": 1436451550454
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@jeff:example.com": {
"ts": 1436451550455
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tina:example.com": {
"ts": 1436451550456
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@sally:example.com": {
"ts": 1436451550457
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@fred:example.com": {
"ts": 1436451550458
}
}
}
},
"type": "m.receipt"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"displayname": "Look\nat\nme\nI\nput\nnewlines\nin\nmy\ndisplay name",
"membership": "join"
},
"event_id": "$143273582443PhrSh:example.org",
"origin_server_ts": 1432735824659,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@newline:example.org",
"state_key": "@newline:example.org",
"type": "m.room.member",
"unsigned": {
"age": 12345
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"avatar_url": "mxc://kde.org/123456",
"displayname": "after",
"membership": "join"
},
"origin_server_ts": 1690651134736,
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"replaces_state": "$1234567890:example.org",
"prev_content": {
"avatar_url": "mxc://kde.org/12345",
"displayname": "before",
"membership": "join"
},
"prev_sender": "@example:example.orgg",
"age": 1234
},
"event_id": "$143273583553PhrSn:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"content": {
"body": "This is a highlight @bob:kde.org and this is a link https://kde.org",
"format": "org.matrix.custom.html",
"msgtype": "m.text"
},
"event_id": "$1532735824654:example.org",
"origin_server_ts": 1532735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$153456789:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1690322545182,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:matrix.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$163456789:example.org",
"age": 390159120
},
{
"age": 4926305285,
"content": {
"body": "video caption",
"filename": "video.mp4",
"info": {
"duration": 10,
"h": 1080,
"mimetype": "video/mp4",
"size": 62650636,
"w": 1920,
"thumbnail_info": {
"h": 450,
"mimetype": "image/jpeg",
"size": 382249,
"w": 800
},
"thumbnail_url": "mxc://kde.org/2234567"
},
"msgtype": "m.video",
"url": "mxc://kde.org/1234567"
},
"event_id": "$263456789:example.org",
"origin_server_ts": 1685793783330,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 4926305285
},
"user_id": "@example:example.org"
},
{
"content": {
"body": "> <@example:example.org> This is an example\ntext message\n\nreply",
"format": "org.matrix.custom.html",
"formatted_body": "<mx-reply><blockquote><a href=\"https://matrix.to/#/!jEsUZKDJdhlrceRyVU:example.org/$153456789:example.org?via=kde.org&via=matrix.org\">In reply to</a> <a href=\"https://matrix.to/#/@example:example.org\">@example:example.org</a><br><b>This is an example<br>text message</b></blockquote></mx-reply>reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$153456789:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1690725965572,
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$154456789:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"content": {
"body": "> <@example:example.org> video caption\n\nreply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$263456789:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1690725965573,
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$154456799:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1544567999:example.org",
"origin_server_ts": 1690821582876,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
},
{
"content": {
"body": "Thread root",
"format": "org.matrix.custom.html",
"msgtype": "m.text"
},
"event_id": "$threadroot:example.org",
"origin_server_ts": 1690821582879,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "Thread message 1",
"msgtype": "m.text",
"m.relates_to": {
"rel_type": "m.thread",
"event_id": "$threadroot:example.org",
"m.in_reply_to": {
"event_id": "$threadroot:example.org"
},
"is_falling_back": true
}
},
"event_id": "$threadmessage1:example.org",
"origin_server_ts": 1690821582890,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1238
}
},
{
"content": {
"body": "Thread message 2",
"msgtype": "m.text",
"m.relates_to": {
"rel_type": "m.thread",
"event_id": "$threadroot:example.org",
"m.in_reply_to": {
"event_id": "$threadmessage1:example.org"
},
"is_falling_back": true
}
},
"event_id": "$threadmessage2:example.org",
"origin_server_ts": 1690821582890,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1238
}
},
{
"content": {
"body": "A message from someone who thought it was a good idea to put newlines in their display name.",
"msgtype": "m.text"
},
"event_id": "$153456889:example.org",
"origin_server_ts": 14327358246589,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@newline:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1230
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -1,87 +0,0 @@
{
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -1,124 +0,0 @@
{
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an **example** text message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example text message</b>",
"msgtype": "m.text"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "/me This is an emote.",
"format": "org.matrix.custom.html",
"formatted_body": "This is an emote.",
"msgtype": "m.emote"
},
"event_id": "$153273582443PhrSn:example.org",
"origin_server_ts": 1532735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1231
}
},
{
"content": {
"body": "tested",
"msgtype": "m.text"
},
"event_id": "$zrCiBxBnqqTn0Z5FY78qSZAszno_w8nJJXzfBULG-3E",
"origin_server_ts": 1680948575928,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1747776,
"m.relations": {
"m.replace": {
"event_id": "$UX0PlpyI7vYO32iHMuuYEP7ECMh4sX3XLGiB2SwM4mQ",
"origin_server_ts": 1680948580992,
"sender": "@example:example.org"
}
}
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -39,63 +39,33 @@ private:
Connection *connection = nullptr;
TestRoom *room = nullptr;
EventHandler eventHandler;
EventHandler emptyHandler;
EventHandler noEventHandler;
private Q_SLOTS:
void initTestCase();
void nullSetEvent();
void eventId();
void nullEventId();
void delegateType_data();
void delegateType();
void nullDelegateType();
void author();
void nullAuthor();
void authorDisplayName();
void nullAuthorDisplayName();
void singleLineSidplayName();
void nullSingleLineDisplayName();
void time();
void nullTime();
void timeString();
void nullTimeString();
void highlighted();
void nullHighlighted();
void hidden();
void nullHidden();
void body();
void nullBody();
void genericBody_data();
void genericBody();
void nullGenericBody();
void mediaInfo();
void nullMediaInfo();
void linkPreviewer();
void nullLinkPreviewer();
void reactions();
void nullReactions();
void hasReply();
void nullHasReply();
void replyId();
void nullReplyId();
void replyDelegateType();
void nullReplyDelegateType();
void replyAuthor();
void nullReplyAuthor();
void replyBody();
void nullReplyBody();
void replyMediaInfo();
void nullReplyMediaInfo();
void thread();
void nullThread();
void location();
void nullLocation();
void readMarkers();
void nullReadMarkers();
void cleanup();
};
void EventHandlerTest::initTestCase()
@@ -103,21 +73,332 @@ void EventHandlerTest::initTestCase()
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testEventHandlerSyncFile;
testEventHandlerSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-eventhandler-sync.json"));
testEventHandlerSyncFile.open(QIODevice::ReadOnly);
const auto testEventHandlerSyncJson = QJsonDocument::fromJson(testEventHandlerSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testEventHandlerSyncJson.object());
const auto json = QJsonDocument::fromJson(R"EVENT({
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
},
{
"content": {
"$153456789:example.org": {
"m.read": {
"@alice:matrix.org": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@bob:example.com": {
"ts": 1436451550453
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tim:example.com": {
"ts": 1436451550454
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@jeff:example.com": {
"ts": 1436451550455
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@tina:example.com": {
"ts": 1436451550456
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@sally:example.com": {
"ts": 1436451550457
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1532735824654:example.org": {
"m.read": {
"@fred:example.com": {
"ts": 1436451550458
}
}
}
},
"type": "m.receipt"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an example\ntext message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example<br>text message</b>",
"msgtype": "m.text"
},
"event_id": "$153456789:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"avatar_url": "mxc://kde.org/123456",
"displayname": "after",
"membership": "join"
},
"origin_server_ts": 1690651134736,
"sender": "@example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"replaces_state": "$1234567890:example.org",
"prev_content": {
"avatar_url": "mxc://kde.org/12345",
"displayname": "before",
"membership": "join"
},
"prev_sender": "@example:example.orgg",
"age": 1234
},
"event_id": "$143273583553PhrSn:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"content": {
"body": "This is a highlight @bob:kde.org and this is a link https://kde.org",
"format": "org.matrix.custom.html",
"msgtype": "m.text"
},
"event_id": "$1532735824654:example.org",
"origin_server_ts": 1532735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$153456789:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1690322545182,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:matrix.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$163456789:example.org",
"age": 390159120
},
{
"age": 4926305285,
"content": {
"body": "video caption",
"filename": "video.mp4",
"info": {
"duration": 10,
"h": 1080,
"mimetype": "video/mp4",
"size": 62650636,
"w": 1920,
"thumbnail_info": {
"h": 450,
"mimetype": "image/jpeg",
"size": 382249,
"w": 800
},
"thumbnail_url": "mxc://kde.org/2234567"
},
"msgtype": "m.video",
"url": "mxc://kde.org/1234567"
},
"event_id": "$263456789:example.org",
"origin_server_ts": 1685793783330,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 4926305285
},
"user_id": "@example:example.org"
},
{
"content": {
"body": "> <@example:example.org> This is an example\ntext message\n\nreply",
"format": "org.matrix.custom.html",
"formatted_body": "<mx-reply><blockquote><a href=\"https://matrix.to/#/!jEsUZKDJdhlrceRyVU:example.org/$153456789:example.org?via=kde.org&via=matrix.org\">In reply to</a> <a href=\"https://matrix.to/#/@example:example.org\">@example:example.org</a><br><b>This is an example<br>text message</b></blockquote></mx-reply>reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$153456789:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1690725965572,
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$154456789:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"content": {
"body": "> <@example:example.org> video caption\n\nreply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$263456789:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1690725965573,
"sender": "@alice:matrix.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$154456799:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1544567999:example.org",
"origin_server_ts": 1690821582876,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
})EVENT");
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
room->update(std::move(roomData));
eventHandler.setRoom(room);
noEventHandler.setRoom(room);
}
void EventHandlerTest::nullSetEvent()
{
QTest::ignoreMessage(QtWarningMsg, "cannot setEvent when m_room is set to nullptr.");
emptyHandler.setEvent(room->messageEvents().at(0).get());
}
void EventHandlerTest::eventId()
@@ -127,12 +408,6 @@ void EventHandlerTest::eventId()
QCOMPARE(eventHandler.getId(), QStringLiteral("$153456789:example.org"));
}
void EventHandlerTest::nullEventId()
{
QTest::ignoreMessage(QtWarningMsg, "getId called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getId(), QString());
}
void EventHandlerTest::delegateType_data()
{
QTest::addColumn<int>("eventNum");
@@ -156,12 +431,6 @@ void EventHandlerTest::delegateType()
QCOMPARE(eventHandler.getDelegateType(), delegateType);
}
void EventHandlerTest::nullDelegateType()
{
QTest::ignoreMessage(QtWarningMsg, "getDelegateType called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getDelegateType(), DelegateType::Other);
}
void EventHandlerTest::author()
{
auto event = room->messageEvents().at(0).get();
@@ -179,15 +448,6 @@ void EventHandlerTest::author()
QCOMPARE(eventHandlerAuthor["object"_ls], QVariant::fromValue(author));
}
void EventHandlerTest::nullAuthor()
{
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getAuthor(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user.");
QCOMPARE(noEventHandler.getAuthor(), room->getUser(nullptr));
}
void EventHandlerTest::authorDisplayName()
{
auto event = room->messageEvents().at(1).get();
@@ -196,32 +456,6 @@ void EventHandlerTest::authorDisplayName()
QCOMPARE(eventHandler.getAuthorDisplayName(), QStringLiteral("before"));
}
void EventHandlerTest::nullAuthorDisplayName()
{
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getAuthorDisplayName(), QString());
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getAuthorDisplayName(), QString());
}
void EventHandlerTest::singleLineSidplayName()
{
auto event = room->messageEvents().at(11).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.singleLineAuthorDisplayname(), QStringLiteral("Look at me I put newlines in my display name"));
}
void EventHandlerTest::nullSingleLineDisplayName()
{
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
QCOMPARE(emptyHandler.singleLineAuthorDisplayname(), QString());
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
QCOMPARE(noEventHandler.singleLineAuthorDisplayname(), QString());
}
void EventHandlerTest::time()
{
auto event = room->messageEvents().at(0).get();
@@ -231,16 +465,6 @@ void EventHandlerTest::time()
QCOMPARE(eventHandler.getTime(true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
}
void EventHandlerTest::nullTime()
{
QTest::ignoreMessage(QtWarningMsg, "getTime called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getTime(), QDateTime());
eventHandler.setEvent(room->messageEvents().at(0).get());
QTest::ignoreMessage(QtWarningMsg, "a value must be provided for lastUpdated for a pending event.");
QCOMPARE(eventHandler.getTime(true), QDateTime());
}
void EventHandlerTest::timeString()
{
auto event = room->messageEvents().at(0).get();
@@ -262,16 +486,6 @@ void EventHandlerTest::timeString()
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
}
void EventHandlerTest::nullTimeString()
{
QTest::ignoreMessage(QtWarningMsg, "getTimeString called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getTimeString(false), QString());
eventHandler.setEvent(room->messageEvents().at(0).get());
QTest::ignoreMessage(QtWarningMsg, "a value must be provided for lastUpdated for a pending event.");
QCOMPARE(eventHandler.getTimeString(false, QLocale::ShortFormat, true), QString());
}
void EventHandlerTest::highlighted()
{
auto event = room->messageEvents().at(2).get();
@@ -285,15 +499,6 @@ void EventHandlerTest::highlighted()
QCOMPARE(eventHandler.isHighlighted(), false);
}
void EventHandlerTest::nullHighlighted()
{
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_room set to nullptr.");
QCOMPARE(emptyHandler.isHighlighted(), false);
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_event set to nullptr.");
QCOMPARE(noEventHandler.isHighlighted(), false);
}
void EventHandlerTest::hidden()
{
auto event = room->messageEvents().at(3).get();
@@ -307,15 +512,6 @@ void EventHandlerTest::hidden()
QCOMPARE(eventHandler.isHidden(), false);
}
void EventHandlerTest::nullHidden()
{
QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_room set to nullptr.");
QCOMPARE(emptyHandler.isHidden(), false);
QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_event set to nullptr.");
QCOMPARE(noEventHandler.isHidden(), false);
}
void EventHandlerTest::body()
{
auto event = room->messageEvents().at(0).get();
@@ -327,15 +523,6 @@ void EventHandlerTest::body()
QCOMPARE(eventHandler.getPlainBody(true), QStringLiteral("This is an example text message"));
}
void EventHandlerTest::nullBody()
{
QTest::ignoreMessage(QtWarningMsg, "getRichBody called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getRichBody(), QString());
QTest::ignoreMessage(QtWarningMsg, "getPlainBody called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getPlainBody(), QString());
}
void EventHandlerTest::genericBody_data()
{
QTest::addColumn<int>("eventNum");
@@ -358,12 +545,6 @@ void EventHandlerTest::genericBody()
QCOMPARE(eventHandler.getGenericBody(), output);
}
void EventHandlerTest::nullGenericBody()
{
QTest::ignoreMessage(QtWarningMsg, "getGenericBody called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getGenericBody(), QString());
}
void EventHandlerTest::mediaInfo()
{
auto event = room->messageEvents().at(4).get();
@@ -387,15 +568,6 @@ void EventHandlerTest::mediaInfo()
QCOMPARE(thumbnailInfo["height"_ls], 450);
}
void EventHandlerTest::nullMediaInfo()
{
QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getMediaInfo(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
}
void EventHandlerTest::linkPreviewer()
{
auto event = room->messageEvents().at(2).get();
@@ -409,15 +581,6 @@ void EventHandlerTest::linkPreviewer()
QCOMPARE(eventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::nullLinkPreviewer()
{
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getLinkPreviewer(), nullptr);
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::reactions()
{
auto event = room->messageEvents().at(0).get();
@@ -426,15 +589,6 @@ void EventHandlerTest::reactions()
QCOMPARE(eventHandler.getReactions()->rowCount(), 1);
}
void EventHandlerTest::nullReactions()
{
QTest::ignoreMessage(QtWarningMsg, "getReactions called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReactions(), nullptr);
QTest::ignoreMessage(QtWarningMsg, "getReactions called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReactions(), nullptr);
}
void EventHandlerTest::hasReply()
{
auto event = room->messageEvents().at(5).get();
@@ -448,12 +602,6 @@ void EventHandlerTest::hasReply()
QCOMPARE(eventHandler.hasReply(), false);
}
void EventHandlerTest::nullHasReply()
{
QTest::ignoreMessage(QtWarningMsg, "hasReply called with m_event set to nullptr.");
QCOMPARE(noEventHandler.hasReply(), false);
}
void EventHandlerTest::replyId()
{
auto event = room->messageEvents().at(5).get();
@@ -467,12 +615,6 @@ void EventHandlerTest::replyId()
QCOMPARE(eventHandler.getReplyId(), QStringLiteral(""));
}
void EventHandlerTest::nullReplyId()
{
QTest::ignoreMessage(QtWarningMsg, "getReplyId called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyId(), QString());
}
void EventHandlerTest::replyDelegateType()
{
auto event = room->messageEvents().at(5).get();
@@ -486,15 +628,6 @@ void EventHandlerTest::replyDelegateType()
QCOMPARE(eventHandler.getReplyDelegateType(), DelegateType::Other);
}
void EventHandlerTest::nullReplyDelegateType()
{
QTest::ignoreMessage(QtWarningMsg, "getReplyDelegateType called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReplyDelegateType(), DelegateType::Other);
QTest::ignoreMessage(QtWarningMsg, "getReplyDelegateType called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyDelegateType(), DelegateType::Other);
}
void EventHandlerTest::replyAuthor()
{
auto event = room->messageEvents().at(5).get();
@@ -518,15 +651,6 @@ void EventHandlerTest::replyAuthor()
QCOMPARE(eventHandler.getReplyAuthor(), room->getUser(nullptr));
}
void EventHandlerTest::nullReplyAuthor()
{
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReplyAuthor(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user.");
QCOMPARE(noEventHandler.getReplyAuthor(), room->getUser(nullptr));
}
void EventHandlerTest::replyBody()
{
auto event = room->messageEvents().at(5).get();
@@ -538,15 +662,6 @@ void EventHandlerTest::replyBody()
QCOMPARE(eventHandler.getReplyPlainBody(true), QStringLiteral("This is an example text message"));
}
void EventHandlerTest::nullReplyBody()
{
QTest::ignoreMessage(QtWarningMsg, "getReplyRichBody called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyRichBody(), QString());
QTest::ignoreMessage(QtWarningMsg, "getReplyPlainBody called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyPlainBody(), QString());
}
void EventHandlerTest::replyMediaInfo()
{
auto event = room->messageEvents().at(6).get();
@@ -571,47 +686,6 @@ void EventHandlerTest::replyMediaInfo()
QCOMPARE(thumbnailInfo["height"_ls], 450);
}
void EventHandlerTest::nullReplyMediaInfo()
{
QTest::ignoreMessage(QtWarningMsg, "getReplyMediaInfo called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReplyMediaInfo(), QVariantMap());
QTest::ignoreMessage(QtWarningMsg, "getReplyMediaInfo called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReplyMediaInfo(), QVariantMap());
}
void EventHandlerTest::thread()
{
auto event = room->messageEvents().at(0).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.isThreaded(), false);
QCOMPARE(eventHandler.threadRoot(), QString());
event = room->messageEvents().at(9).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.isThreaded(), true);
QCOMPARE(eventHandler.threadRoot(), QStringLiteral("$threadroot:example.org"));
QCOMPARE(eventHandler.getReplyId(), QStringLiteral("$threadroot:example.org"));
event = room->messageEvents().at(10).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.isThreaded(), true);
QCOMPARE(eventHandler.threadRoot(), QStringLiteral("$threadroot:example.org"));
QCOMPARE(eventHandler.getReplyId(), QStringLiteral("$threadmessage1:example.org"));
}
void EventHandlerTest::nullThread()
{
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with m_event set to nullptr.");
QCOMPARE(emptyHandler.isThreaded(), false);
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with m_event set to nullptr.");
QCOMPARE(noEventHandler.threadRoot(), QString());
}
void EventHandlerTest::location()
{
auto event = room->messageEvents().at(7).get();
@@ -622,18 +696,6 @@ void EventHandlerTest::location()
QCOMPARE(eventHandler.getLocationAssetType(), QStringLiteral("m.pin"));
}
void EventHandlerTest::nullLocation()
{
QTest::ignoreMessage(QtWarningMsg, "getLatitude called with m_event set to nullptr.");
QCOMPARE(emptyHandler.getLatitude(), -100.0);
QTest::ignoreMessage(QtWarningMsg, "getLongitude called with m_event set to nullptr.");
QCOMPARE(emptyHandler.getLongitude(), -200.0);
QTest::ignoreMessage(QtWarningMsg, "getLocationAssetType called with m_event set to nullptr.");
QCOMPARE(emptyHandler.getLocationAssetType(), QString());
}
void EventHandlerTest::readMarkers()
{
auto event = room->messageEvents().at(0).get();
@@ -663,37 +725,5 @@ void EventHandlerTest::readMarkers()
QCOMPARE(eventHandler.getReadMarkersString().startsWith(QStringLiteral("6 users:")), true);
}
void EventHandlerTest::nullReadMarkers()
{
QTest::ignoreMessage(QtWarningMsg, "hasReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReadMarkers(), QVariantList());
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getNumberExcessReadMarkers(), QString());
QTest::ignoreMessage(QtWarningMsg, "getReadMarkersString called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getReadMarkersString(), QString());
QTest::ignoreMessage(QtWarningMsg, "hasReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.hasReadMarkers(), false);
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReadMarkers(), QVariantList());
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getNumberExcessReadMarkers(), QString());
QTest::ignoreMessage(QtWarningMsg, "getReadMarkersString called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getReadMarkersString(), QString());
}
void EventHandlerTest::cleanup()
{
eventHandler.setEvent(nullptr);
}
QTEST_MAIN(EventHandlerTest)
#include "eventhandlertest.moc"

View File

@@ -42,18 +42,101 @@ void NeoChatRoomTest::initTestCase()
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testMinSyncFile;
testMinSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-min-sync.json"));
testMinSyncFile.open(QIODevice::ReadOnly);
const auto testMinSyncJson = QJsonDocument::fromJson(testMinSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testMinSyncJson.object());
auto json = QJsonDocument::fromJson(R"EVENT({
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an **example** text message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example text message</b>",
"msgtype": "m.text"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1235
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
})EVENT");
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
room->update(std::move(roomData));
}
void NeoChatRoomTest::subtitleTextTest()
{
QCOMPARE(room->timelineSize(), 1);
QCOMPARE(room->lastEventToString(), QStringLiteral("@example:example.org: This is an example\ntext message"));
QCOMPARE(room->lastEventToString(), QStringLiteral("@example:example.org: This is an example text message"));
}
void NeoChatRoomTest::eventTest()

View File

@@ -40,7 +40,6 @@ private Q_SLOTS:
void stripDisallowedTags();
void stripDisallowedAttributes();
void emptyCodeTags();
void formatBlockQuote();
void sendSimpleStringCase();
void sendSingleParaMarkup();
@@ -67,7 +66,6 @@ private Q_SLOTS:
void receiveRichEdited_data();
void receiveRichEdited();
void receiveLineSeparator();
void receiveRichCodeUrl();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
@@ -80,11 +78,131 @@ void TextHandlerTest::initTestCase()
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
QFile testTextHandlerSyncFile;
testTextHandlerSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-texthandler-sync.json"));
testTextHandlerSyncFile.open(QIODevice::ReadOnly);
const auto testTextHandlerSyncJson = QJsonDocument::fromJson(testTextHandlerSyncFile.readAll());
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testTextHandlerSyncJson.object());
const auto json = QJsonDocument::fromJson(R"EVENT({
"account_data": {
"events": [
{
"content": {
"tags": {
"u.work": {
"order": 0.9
}
}
},
"type": "m.tag"
},
{
"content": {
"custom_config_key": "custom_config_value"
},
"type": "org.example.custom.room.config"
}
]
},
"ephemeral": {
"events": [
{
"content": {
"user_ids": [
"@alice:matrix.org",
"@bob:example.com"
]
},
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"type": "m.typing"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice Margatroid",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824653,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"summary": {
"m.heroes": [
"@alice:example.com",
"@bob:example.com"
],
"m.invited_member_count": 0,
"m.joined_member_count": 2
},
"timeline": {
"events": [
{
"content": {
"body": "This is an **example** text message",
"format": "org.matrix.custom.html",
"formatted_body": "<b>This is an example text message</b>",
"msgtype": "m.text"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "/me This is an emote.",
"format": "org.matrix.custom.html",
"formatted_body": "This is an emote.",
"msgtype": "m.emote"
},
"event_id": "$153273582443PhrSn:example.org",
"origin_server_ts": 1532735824654,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1231
}
},
{
"content": {
"body": "tested",
"msgtype": "m.text"
},
"event_id": "$zrCiBxBnqqTn0Z5FY78qSZAszno_w8nJJXzfBULG-3E",
"origin_server_ts": 1680948575928,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1747776,
"m.relations": {
"m.replace": {
"event_id": "$UX0PlpyI7vYO32iHMuuYEP7ECMh4sX3XLGiB2SwM4mQ",
"origin_server_ts": 1680948580992,
"sender": "@example:example.org"
}
}
}
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
})EVENT");
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
room->update(std::move(roomData));
}
@@ -147,16 +265,6 @@ void TextHandlerTest::emptyCodeTags()
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
}
void TextHandlerTest::formatBlockQuote()
{
auto input = QStringLiteral("<blockquote>\n<p>Lorem Ispum</p>\n</blockquote>");
auto expectedOutput = QStringLiteral("<blockquote><table><tr><td>\u201CLorem Ispum\u201D</td></tr></table></blockquote>");
TextHandler testTextHandler;
testTextHandler.setData(input);
QCOMPARE(testTextHandler.handleRecieveRichText(), expectedOutput);
}
void TextHandlerTest::sendSimpleStringCase()
{
const QString testInputString = QStringLiteral("This data should just be put in a paragraph.");
@@ -469,9 +577,8 @@ void TextHandlerTest::receiveRichEdited_data()
QTest::newRow("basic") << QStringLiteral("Edited") << QStringLiteral("Edited <span style=\"color:#000000\">(edited)</span>");
QTest::newRow("multiple paragraphs") << QStringLiteral("<p>Edited</p>\n<p>Edited</p>")
<< QStringLiteral("<p>Edited</p>\n<p>Edited <span style=\"color:#000000\">(edited)</span></p>");
QTest::newRow("blockquote")
<< QStringLiteral("<blockquote>Edited</blockquote>")
<< QStringLiteral("<blockquote><table><tr><td>\u201CEdited\u201D</td></tr></table></blockquote><p> <span style=\"color:#000000\">(edited)</span></p>");
QTest::newRow("blockquote") << QStringLiteral("<blockquote>Edited</blockquote>")
<< QStringLiteral("<blockquote>Edited</blockquote><p> <span style=\"color:#000000\">(edited)</span></p>");
}
void TextHandlerTest::receiveRichEdited()
@@ -540,13 +647,5 @@ void TextHandlerTest::linkPreviewsReject()
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::receiveRichCodeUrl()
{
auto input = QStringLiteral("<code>https://kde.org</code>");
TextHandler testTextHandler;
testTextHandler.setData(input);
QCOMPARE(testTextHandler.handleRecieveRichText(), input);
}
QTEST_MAIN(TextHandlerTest)
#include "texthandlertest.moc"

View File

@@ -2,4 +2,4 @@
#
# SPDX-License-Identifier: GPL-3.0-or-later
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${KDE_INSTALL_MANDIR})
kdoctools_create_manpage(man-neochat.1.docbook 1 INSTALL_DESTINATION ${MAN_INSTALL_DIR})

View File

@@ -11,7 +11,6 @@
</provides>
<name>NeoChat</name>
<name xml:lang="ar">نيوتشات</name>
<name xml:lang="ast">NeoChat</name>
<name xml:lang="az">NeoChat</name>
<name xml:lang="ca">NeoChat</name>
<name xml:lang="ca-valencia">NeoChat</name>
@@ -54,7 +53,6 @@
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
<summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
<summary xml:lang="ia">Starta Conversation conntu amicos sur matrix</summary>
@@ -99,7 +97,7 @@ to provide a convergent experience across multiple platforms.</p>
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
<p xml:lang="en-GB">NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
<p xml:lang="eo">NeoChat celas esti plene kapabla aplikaĵo por la Matrix-specifo. Kiel tia, ĉio en la nuna stabila specifo kun la rimarkindaj esceptoj de VoIP, fadenoj kaj kelkaj aspektoj de Fin-al-Fina Ĉifrado estas subtenataj. Estas kelkaj aliaj pli malgrandaj preterlasoj pro la fakto, ke la Matrix-speco konstante evoluas, sed la celo restas provizi finfine subtenon por la tuta specifaĵo.</p>
<p xml:lang="es">NeoChat pretende ser una aplicación con todas las funciones para la especificación de Matrix. Como tal, admite todo en la especificación estable actual, con las notables excepciones de VoIP, subprocesos y algunas funciones de cifrado de extremo a extremo. Existen algunas omisiones menos importantes debido al hecho de que la especificación de Matrix está en constante evolución, pero el objetivo sigue siendo brindar compatibilidad final con toda la especificación.</p>
@@ -284,7 +282,7 @@ to provide a convergent experience across multiple platforms.</p>
<screenshot type="default">
<image>https://cdn.kde.org/screenshots/neochat/application.png</image>
</screenshot>
<screenshot environment="windows">
<screenshot x-kde-os="windows">
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</image>
<caption>Main view with room list, chat, and room information</caption>
<caption xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</caption>
@@ -311,7 +309,7 @@ to provide a convergent experience across multiple platforms.</p>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
</screenshot>
<screenshot environment="windows">
<screenshot x-kde-os="windows">
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</image>
<caption>Login screen</caption>
<caption xml:lang="ar">شاشة الدخول</caption>
@@ -343,8 +341,6 @@ to provide a convergent experience across multiple platforms.</p>
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="23.08.3" date="2023-11-09"/>
<release version="23.08.2" date="2023-10-12"/>
<release version="23.08.0" date="2023-08-24">
<url>https://kde.org/announcements/gear/23.08.0/#neochathttpsappskdeorgneochat</url>
<description>

View File

@@ -4,7 +4,6 @@
Version=1.5
Name=NeoChat
Name[ar]=نيوتشات
Name[ast]=NeoChat
Name[az]=NeoChat
Name[ca]=NeoChat
Name[ca@valencia]=NeoChat

View File

@@ -1,8 +1 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<style type="text/css" id="current-color-scheme">.ColorScheme-Text{color:#232629}</style>
<path class="ColorScheme-Text" fill-rule="evenodd" clip-rule="evenodd" d="M3 3H19V14H8.68787L4 18.1019V14H3V3ZM4 13H5V15.8981L8.31213 13H18V4H4V13Z" fill="currentColor"/>
<path class="ColorScheme-Text" fill-rule="evenodd" clip-rule="evenodd" d="M17 15.2929L14.8536 13.1465L14.1465 13.8536L18 17.7071V13.5H17V15.2929Z" fill="currentColor"/>
<path class="ColorScheme-Text" d="M5 6H15V7H5V6Z" fill="currentColor"/>
<path class="ColorScheme-Text" d="M5 8H13V9H5V8Z" fill="currentColor"/>
<path class="ColorScheme-Text" d="M5 10H11V11H5V10Z" fill="currentColor"/>
</svg>
<svg width="22" height="22" fill="none" version="1.1" id="svg13" xmlns="http://www.w3.org/2000/svg"><style type="text/css" id="current-color-scheme">.ColorScheme-Text{color:#232629}</style><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="M2 4h18v11H6.681L3 18.067V15H2zm1 10h1v1.933L6.319 14H19V5H3z" id="path3"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect5" d="M4 7h9v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect7" d="M4 9h7v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" id="rect9" d="M4 11h5v1H4z"/><path class="ColorScheme-Text" style="fill:currentColor;fill-opacity:1;stroke:none" fill-rule="evenodd" clip-rule="evenodd" d="m16 15.293-1.147-1.146-.707.707 2.853 2.853V14.5h-1z" id="path11"/></svg>

Before

Width:  |  Height:  |  Size: 752 B

After

Width:  |  Height:  |  Size: 928 B

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -40,12 +40,6 @@ add_library(neochat STATIC
models/userfiltermodel.h
models/publicroomlistmodel.cpp
models/publicroomlistmodel.h
models/spacechildrenmodel.cpp
models/spacechildrenmodel.h
models/spacechildsortfiltermodel.cpp
models/spacechildsortfiltermodel.h
models/spacetreeitem.cpp
models/spacetreeitem.h
models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h
models/pushrulemodel.cpp
@@ -133,16 +127,6 @@ add_library(neochat STATIC
mediasizehelper.h
eventhandler.cpp
enums/delegatetype.h
roomlastmessageprovider.cpp
roomlastmessageprovider.h
chatbarcache.cpp
chatbarcache.h
colorschemer.cpp
colorschemer.h
models/notificationsmodel.cpp
models/notificationsmodel.h
models/timelinemodel.cpp
models/timelinemodel.h
)
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
@@ -150,33 +134,33 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/main.qml
qml/AccountMenu.qml
qml/ExploreComponent.qml
qml/ExploreComponentMobile.qml
qml/ContextMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomDelegate.qml
qml/RoomListPage.qml
qml/SpaceListContextMenu.qml
qml/UserInfo.qml
qml/UserInfoDesktop.qml
qml/LoadingPage.qml
qml/RoomPage.qml
qml/RoomWindow.qml
qml/JoinRoomPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/StartChatPage.qml
qml/ImageEditorPage.qml
qml/WelcomePage.qml
qml/General.qml
qml/RoomSecurity.qml
qml/Security.qml
qml/PushNotification.qml
qml/Categories.qml
qml/Permissions.qml
qml/NeochatMaximizeComponent.qml
qml/FancyEffectsContainer.qml
qml/TypingPane.qml
qml/ShimmerGradient.qml
qml/QuickSwitcher.qml
qml/HoverActions.qml
qml/ChatBox.qml
qml/ChatBar.qml
qml/AttachmentPane.qml
qml/ReplyPane.qml
@@ -223,6 +207,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/Sso.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/CreateSpaceDialog.qml
qml/EmojiDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
@@ -286,26 +271,11 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/RoomMedia.qml
qml/ChooseRoomDialog.qml
qml/ShareAction.qml
qml/SpaceHomePage.qml
qml/SpaceHierarchyDelegate.qml
qml/RemoveChildDialog.qml
qml/SelectParentDialog.qml
qml/Security.qml
qml/QrCodeMaximizeComponent.qml
qml/SelectSpacesDialog.qml
qml/AttachDialog.qml
qml/NotificationsView.qml
qml/LoadingDelegate.qml
qml/TimelineEndDelegate.qml
RESOURCES
qml/confetti.png
qml/glowdot.png
)
if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
ecm_qt_declare_logging_category(neochat
HEADER "messageeventmodel_logging.h"
IDENTIFIER "MessageEvent"
@@ -322,13 +292,6 @@ ecm_qt_declare_logging_category(neochat
DEFAULT_SEVERITY Info
)
ecm_qt_declare_logging_category(neochat
HEADER "chatdocumenthandler_logging.h"
IDENTIFIER "ChatDocumentHandling"
CATEGORY_NAME "org.kde.neochat.chatdocumenthandler"
DEFAULT_SEVERITY Info
)
add_executable(neochat-app
main.cpp
)
@@ -349,15 +312,16 @@ ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
if(NOT ANDROID)
target_sources(neochat PRIVATE colorschemer.cpp colorschemer.h)
if (NOT WIN32 AND NOT APPLE)
target_sources(neochat PRIVATE trayicon_sni.cpp trayicon_sni.h)
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
else()
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
endif()
target_link_libraries(neochat PUBLIC KF6::WindowSystem ICU::uc)
target_link_libraries(neochat PUBLIC KF6::ConfigWidgets KF6::WindowSystem)
target_compile_definitions(neochat PUBLIC -DHAVE_COLORSCHEME)
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
target_compile_definitions(neochat PUBLIC -DHAVE_ICU)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
@@ -367,28 +331,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
target_link_libraries(neochat PUBLIC
Qt::Core
Qt::Quick
Qt::Qml
Qt::Gui
Qt::Multimedia
Qt::Network
Qt::QuickControls2
KF6::I18n
KF6::Kirigami
KF6::Notifications
KF6::ConfigCore
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::ColorScheme
KF6::ItemModels
QuotientQt6
cmark::cmark
QCoro::Core
QCoro::Network
)
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF6::I18n KF6::Kirigami2 KF6::Notifications KF6::ConfigCore KF6::ConfigGui KF6::CoreAddons KF6::SonnetCore KF6::ItemModels QuotientQt6 cmark::cmark QCoro::Core)
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
@@ -474,9 +417,7 @@ if(ANDROID)
"preferences-desktop-notification"
"computer-symbolic"
"gps"
"system-users-symbolic"
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets)
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
@@ -495,18 +436,9 @@ if (TARGET KF6::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()
if (TARGET KUnifiedPush)
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
target_link_libraries(neochat PUBLIC KUnifiedPush)
if (NOT ANDROID)
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
endif()
endif()
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()

View File

@@ -38,33 +38,45 @@ void ActionsHandler::setRoom(NeoChatRoom *room)
Q_EMIT roomChanged();
}
void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
void ActionsHandler::handleNewMessage()
{
if (!chatBarCache) {
return;
}
checkEffects(chatBarCache->text());
if (!chatBarCache->attachmentPath().isEmpty()) {
QUrl url(chatBarCache->attachmentPath());
checkEffects(m_room->chatBoxText());
if (!m_room->chatBoxAttachmentPath().isEmpty()) {
QUrl url(m_room->chatBoxAttachmentPath());
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
m_room->uploadFile(QUrl(path), chatBarCache->text().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : chatBarCache->text());
chatBarCache->setAttachmentPath({});
chatBarCache->setText({});
m_room->uploadFile(QUrl(path), m_room->chatBoxText().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : m_room->chatBoxText());
m_room->setChatBoxAttachmentPath({});
m_room->setChatBoxText({});
return;
}
QString handledText = chatBarCache->text();
handledText = handleMentions(handledText, chatBarCache->mentions());
handleMessage(m_room->mainCache()->text(), handledText, chatBarCache);
QString handledText = m_room->chatBoxText();
handledText = handleMentions(handledText);
handleMessage(m_room->chatBoxText(), handledText);
}
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *mentions)
void ActionsHandler::handleEdit()
{
checkEffects(m_room->editText());
QString handledText = m_room->editText();
handledText = handleMentions(handledText, true);
handleMessage(m_room->editText(), handledText, true);
}
QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
{
if (!m_room) {
return QString();
}
QVector<Mention> *mentions;
if (isEdit) {
mentions = m_room->editMentions();
} else {
mentions = m_room->mentions();
}
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
return a.cursor.anchor() > b.cursor.anchor();
});
@@ -75,14 +87,14 @@ QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *ment
}
handledText = handledText.replace(mention.cursor.anchor(),
mention.cursor.position() - mention.cursor.anchor(),
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text, mention.id));
}
mentions->clear();
return handledText;
}
void ActionsHandler::handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache)
void ActionsHandler::handleMessage(const QString &text, QString handledText, const bool &isEdit)
{
if (NeoChatConfig::allowQuickEdit()) {
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
@@ -122,7 +134,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
for (const auto &action : ActionsModel::instance().allActions()) {
if (handledText.indexOf(action.prefix) == 1
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room, chatBarCache);
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room);
if (action.messageType.has_value()) {
messageType = *action.messageType;
}
@@ -149,7 +161,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
return;
}
m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : QString());
}
void ActionsHandler::checkEffects(const QString &text)

View File

@@ -8,7 +8,6 @@
#include <Quotient/events/roommessageevent.h>
#include "chatbarcache.h"
#include "neochatroom.h"
class NeoChatRoom;
@@ -52,15 +51,21 @@ Q_SIGNALS:
void showEffect(const QString &effect);
public Q_SLOTS:
/**
* @brief Pre-process text and send message event.
* @brief Pre-process text and send message.
*/
void handleMessageEvent(ChatBarCache *chatBarCache);
void handleNewMessage();
/**
* @brief Pre-process text and send edit.
*/
void handleEdit();
private:
NeoChatRoom *m_room = nullptr;
void checkEffects(const QString &text);
QString handleMentions(QString handledText, QList<Mention> *mentions);
void handleMessage(const QString &text, QString handledText, ChatBarCache *chatBarCache);
QString handleMentions(QString handledText, const bool &isEdit = false);
void handleMessage(const QString &text, QString handledText, const bool &isEdit = false);
};

View File

@@ -177,7 +177,7 @@ uint8_t *decode(const char *blurhash, int width, int height, int punch, int nCha
uint8_t *pixelArray = createByteArray(bytesPerRow * height);
if (decodeToArray(blurhash, width, height, punch, nChannels, pixelArray) == -1) {
return nullptr;
return NULL;
}
return pixelArray;
}

View File

@@ -1,177 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "chatbarcache.h"
#include "eventhandler.h"
#include "neochatroom.h"
ChatBarCache::ChatBarCache(QObject *parent)
: QObject(parent)
{
}
QString ChatBarCache::text() const
{
return m_text;
}
void ChatBarCache::setText(const QString &text)
{
if (text == m_text) {
return;
}
m_text = text;
Q_EMIT textChanged();
}
bool ChatBarCache::isReplying() const
{
return m_relationType == Reply && !m_relationId.isEmpty();
}
QString ChatBarCache::replyId() const
{
if (m_relationType != Reply) {
return {};
}
return m_relationId;
}
void ChatBarCache::setReplyId(const QString &replyId)
{
if (m_relationType == Reply && m_relationId == replyId) {
return;
}
m_relationId = replyId;
if (m_relationId.isEmpty()) {
m_relationType = None;
} else {
m_relationType = Reply;
}
m_attachmentPath = QString();
Q_EMIT relationIdChanged();
Q_EMIT attachmentPathChanged();
}
bool ChatBarCache::isEditing() const
{
return m_relationType == Edit && !m_relationId.isEmpty();
}
QString ChatBarCache::editId() const
{
if (m_relationType != Edit) {
return {};
}
return m_relationId;
}
void ChatBarCache::setEditId(const QString &editId)
{
if (m_relationType == Edit && m_relationId == editId) {
return;
}
m_relationId = editId;
if (m_relationId.isEmpty()) {
m_relationType = None;
} else {
m_relationType = Edit;
}
m_attachmentPath = QString();
Q_EMIT relationIdChanged();
Q_EMIT attachmentPathChanged();
}
QVariantMap ChatBarCache::relationUser() const
{
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
if (m_relationId.isEmpty()) {
return room->getUser(nullptr);
}
return room->getUser(room->user((*room->findInTimeline(m_relationId))->senderId()));
}
QString ChatBarCache::relationMessage() const
{
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
if (m_relationId.isEmpty()) {
return {};
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return {};
}
EventHandler eventhandler;
eventhandler.setRoom(room);
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
eventhandler.setEvent(&**event);
return eventhandler.getPlainBody();
}
return {};
}
bool ChatBarCache::isThreaded() const
{
return !m_threadId.isEmpty();
}
QString ChatBarCache::threadId() const
{
return m_threadId;
}
void ChatBarCache::setThreadId(const QString &threadId)
{
if (m_threadId == threadId) {
return;
}
m_threadId = threadId;
Q_EMIT threadIdChanged();
}
QString ChatBarCache::attachmentPath() const
{
return m_attachmentPath;
}
void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
{
if (attachmentPath == m_attachmentPath) {
return;
}
m_attachmentPath = attachmentPath;
m_relationType = None;
m_relationId = QString();
Q_EMIT attachmentPathChanged();
Q_EMIT relationIdChanged();
}
QList<Mention> *ChatBarCache::mentions()
{
return &m_mentions;
}
QString ChatBarCache::savedText() const
{
return m_savedText;
}
void ChatBarCache::setSavedText(const QString &savedText)
{
m_savedText = savedText;
}
#include "moc_chatbarcache.cpp"

View File

@@ -1,201 +0,0 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QTextCursor>
/**
* @brief Defines a user mention in the current chat or edit text.
*/
struct Mention {
QTextCursor cursor; /**< Contains the mention's text and position in the text. */
QString text; /**< The inserted text of the mention. */
int start = 0; /**< Start position of the mention. */
int position = 0; /**< End position of the mention. */
QString id; /**< The id the mention (used to create link when sending the message). */
};
/**
* @class ChatBarCache
*
* A class to cache data from a chat bar.
*
* A chat bar can be anything that allows users to compose or edit message, it doesn't
* necessarily have to use the ChatBar component, e.g. MessageEditComponent.
*
* This object is intended to allow the current contents of a chat bar to be cached
* between different rooms, i.e. there is an expectation that each NeoChatRoom could
* have a separate cache for each chat bar.
*
* @note The NeoChatRoom which this component is created in is expected to be set
* as it's parent. This is necessary for certain functions which need to get
* relevant room information.
*
* @sa ChatBar, MessageEditComponent, NeoChatRoom
*/
class ChatBarCache : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
/**
* @brief The text in the chat bar.
*
* Due to problems with QTextDocument, unlike the other properties here,
* text is *not* used to store the text when switching rooms.
*/
Q_PROPERTY(QString text READ text WRITE setText NOTIFY textChanged)
/**
* @brief Whether the chat bar is currently replying to a message.
*/
Q_PROPERTY(bool isReplying READ isReplying NOTIFY relationIdChanged)
/**
* @brief The Matrix message ID of an event being replied to, if any.
*
* Will return empty if the RelationType is currently set to None or Edit.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an edit or attachment.
*
* @sa RelationType
*/
Q_PROPERTY(QString replyId READ replyId WRITE setReplyId NOTIFY relationIdChanged)
/**
* @brief Whether the chat bar is currently editing a message.
*/
Q_PROPERTY(bool isEditing READ isEditing NOTIFY relationIdChanged)
/**
* @brief The Matrix message ID of an event being edited, if any.
*
* Will return empty if the RelationType is currently set to None or Reply.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an reply or attachment.
*
* @sa RelationType
*/
Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged)
/**
* @brief Get the user for the message being replied to.
*
* This is different to getting a Quotient::User object
* as neither of those can provide details like the displayName or avatarMediaId
* without the room context as these can vary from room to room.
*
* Returns an empty user if not replying to a message.
*
* The user QVariantMap has the following properties:
* - isLocalUser - Whether the user is the local user.
* - id - The matrix ID of the user.
* - displayName - Display name in the context of this room.
* - avatarSource - The mxc URL for the user's avatar in the current room.
* - avatarMediaId - Avatar id in the context of this room.
* - color - Color for the user.
* - object - The Quotient::User object for the user.
*
* @sa getUser, Quotient::User
*/
Q_PROPERTY(QVariantMap relationUser READ relationUser NOTIFY relationIdChanged)
/**
* @brief The content of the related message.
*
* Will be QString() if no related message.
*/
Q_PROPERTY(QString relationMessage READ relationMessage NOTIFY relationIdChanged)
/**
* @brief Whether the chat bar is replying in a thread.
*/
Q_PROPERTY(bool isThreaded READ isThreaded NOTIFY threadIdChanged)
/**
* @brief The Matrix message ID of thread root event, if any.
*/
Q_PROPERTY(QString threadId READ threadId WRITE setThreadId NOTIFY threadIdChanged)
/**
* @brief The local path for a file to send, if any.
*
* @note Replying, editing and attachments are exclusive so setting this will
* clear an edit or reply.
*/
Q_PROPERTY(QString attachmentPath READ attachmentPath WRITE setAttachmentPath NOTIFY attachmentPathChanged)
public:
/**
* @brief Describes the type of relation which relationId can refer to.
*
* A chat bar can only be relating to a single message at a time making these
* exclusive.
*/
enum RelationType {
Reply, /**< The current relation is a message being replied to. */
Edit, /**< The current relation is a message being edited. */
None, /**< There is currently no relation event */
};
Q_ENUM(RelationType)
explicit ChatBarCache(QObject *parent = nullptr);
QString text() const;
void setText(const QString &text);
bool isReplying() const;
QString replyId() const;
void setReplyId(const QString &replyId);
bool isEditing() const;
QString editId() const;
void setEditId(const QString &editId);
QVariantMap relationUser() const;
QString relationMessage() const;
bool isThreaded() const;
QString threadId() const;
void setThreadId(const QString &threadId);
QString attachmentPath() const;
void setAttachmentPath(const QString &attachmentPath);
/**
* @brief Retrieve the mentions for the current chat bar text.
*/
QList<Mention> *mentions();
/**
* @brief Get the saved chat bar text.
*/
QString savedText() const;
/**
* @brief Save the chat bar text.
*/
void setSavedText(const QString &savedText);
Q_SIGNALS:
void textChanged();
void relationIdChanged();
void threadIdChanged();
void attachmentPathChanged();
private:
QString m_text = QString();
QString m_relationId = QString();
RelationType m_relationType = RelationType::None;
QString m_threadId = QString();
QString m_attachmentPath = QString();
QList<Mention> m_mentions;
QString m_savedText;
};

View File

@@ -14,8 +14,6 @@
#include <Sonnet/BackgroundChecker>
#include <Sonnet/Settings>
#include "chatdocumenthandler_logging.h"
class SyntaxHighlighter : public QSyntaxHighlighter
{
public:
@@ -59,12 +57,11 @@ public:
setFormat(error.first, error.second.size(), errorFormat);
}
}
auto handler = dynamic_cast<ChatDocumentHandler *>(parent());
auto room = handler->room();
auto room = dynamic_cast<ChatDocumentHandler *>(parent())->room();
if (!room) {
return;
}
auto mentions = handler->chatBarCache()->mentions();
auto mentions = room->mentions();
mentions->erase(std::remove_if(mentions->begin(),
mentions->end(),
[this](auto &mention) {
@@ -100,16 +97,21 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
, m_document(nullptr)
, m_cursorPosition(-1)
, m_highlighter(new SyntaxHighlighter(this))
, m_completionModel(new CompletionModel(this))
, m_completionModel(new CompletionModel())
{
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
m_completionModel->setRoom(m_room);
static QPointer<NeoChatRoom> previousRoom = nullptr;
if (previousRoom) {
disconnect(m_chatBarCache, &ChatBarCache::textChanged, this, nullptr);
disconnect(previousRoom, &NeoChatRoom::chatBoxTextChanged, this, nullptr);
disconnect(previousRoom, &NeoChatRoom::editTextChanged, this, nullptr);
}
previousRoom = m_room;
connect(m_chatBarCache, &ChatBarCache::textChanged, this, [this]() {
connect(m_room, &NeoChatRoom::chatBoxTextChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
connect(m_room, &NeoChatRoom::editTextChanged, this, [this]() {
int start = completionStartIndex();
m_completionModel->setText(getText().mid(start, cursorPosition() - start), getText().mid(start));
});
@@ -132,7 +134,11 @@ int ChatDocumentHandler::completionStartIndex() const
return 0;
}
const qsizetype cursor = cursorPosition();
#if !defined(Q_OS_ANDROID)
const long long cursor = cursorPosition();
#else
const auto cursor = cursorPosition();
#endif
const auto &text = getText();
auto start = std::min(cursor, text.size()) - 1;
@@ -146,6 +152,20 @@ int ChatDocumentHandler::completionStartIndex() const
return start;
}
bool ChatDocumentHandler::isEdit() const
{
return m_isEdit;
}
void ChatDocumentHandler::setIsEdit(bool edit)
{
if (edit == m_isEdit) {
return;
}
m_isEdit = edit;
Q_EMIT isEditChanged();
}
QQuickTextDocument *ChatDocumentHandler::document() const
{
return m_document;
@@ -195,31 +215,8 @@ void ChatDocumentHandler::setRoom(NeoChatRoom *room)
Q_EMIT roomChanged();
}
ChatBarCache *ChatDocumentHandler::chatBarCache() const
{
return m_chatBarCache;
}
void ChatDocumentHandler::setChatBarCache(ChatBarCache *chatBarCache)
{
if (m_chatBarCache == chatBarCache) {
return;
}
m_chatBarCache = chatBarCache;
Q_EMIT chatBarCacheChanged();
}
void ChatDocumentHandler::complete(int index)
{
if (m_document == nullptr) {
qCWarning(ChatDocumentHandling) << "complete called with m_document set to nullptr.";
return;
}
if (m_completionModel->autoCompletionType() == CompletionModel::None) {
qCWarning(ChatDocumentHandling) << "complete called with m_completionModel->autoCompletionType() == CompletionModel::None.";
return;
}
if (m_completionModel->autoCompletionType() == CompletionModel::User) {
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::DisplayNameRole).toString();
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString();
@@ -303,20 +300,26 @@ void ChatDocumentHandler::setSelectionEnd(int position)
QString ChatDocumentHandler::getText() const
{
if (!m_chatBarCache) {
qCWarning(ChatDocumentHandling) << "getText called with m_chatBarCache set to nullptr.";
return {};
if (!m_room) {
return QString();
}
if (m_isEdit) {
return m_room->editText();
} else {
return m_room->chatBoxText();
}
return m_chatBarCache->text();
}
void ChatDocumentHandler::pushMention(const Mention mention) const
{
if (!m_chatBarCache) {
qCWarning(ChatDocumentHandling) << "pushMention called with m_chatBarCache set to nullptr.";
if (!m_room) {
return;
}
m_chatBarCache->mentions()->push_back(mention);
if (m_isEdit) {
m_room->editMentions()->push_back(mention);
} else {
m_room->mentions()->push_back(mention);
}
}
QColor ChatDocumentHandler::mentionColor() const

View File

@@ -8,7 +8,6 @@
#include <QQuickTextDocument>
#include <QTextCursor>
#include "chatbarcache.h"
#include "models/completionmodel.h"
#include "neochatroom.h"
@@ -62,6 +61,14 @@ class ChatDocumentHandler : public QObject
Q_OBJECT
QML_ELEMENT
/**
* @brief Is the instance being used to handle an edit message.
*
* This is needed to ensure that the text and mentions are saved and retrieved
* from the correct parameters in the assigned room.
*/
Q_PROPERTY(bool isEdit READ isEdit WRITE setIsEdit NOTIFY isEditChanged)
/**
* @brief The QQuickTextDocument that is being handled.
*/
@@ -88,18 +95,13 @@ class ChatDocumentHandler : public QObject
* This is typically provided to a qml component to visualise the current
* completion results.
*/
Q_PROPERTY(CompletionModel *completionModel READ completionModel CONSTANT)
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
/**
* @brief The current room that the the text document is being handled for.
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
/**
* @brief The cache for the chat bar the text document is being handled for.
*/
Q_PROPERTY(ChatBarCache *chatBarCache READ chatBarCache WRITE setChatBarCache NOTIFY chatBarCacheChanged)
/**
* @brief The color to highlight user mentions.
*/
@@ -113,6 +115,9 @@ class ChatDocumentHandler : public QObject
public:
explicit ChatDocumentHandler(QObject *parent = nullptr);
[[nodiscard]] bool isEdit() const;
void setIsEdit(bool edit);
[[nodiscard]] QQuickTextDocument *document() const;
void setDocument(QQuickTextDocument *document);
@@ -128,11 +133,9 @@ public:
[[nodiscard]] NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
[[nodiscard]] ChatBarCache *chatBarCache() const;
void setChatBarCache(ChatBarCache *chatBarCache);
Q_INVOKABLE void complete(int index);
void updateCompletions();
CompletionModel *completionModel() const;
[[nodiscard]] QColor mentionColor() const;
@@ -142,10 +145,11 @@ public:
void setErrorColor(const QColor &color);
Q_SIGNALS:
void isEditChanged();
void documentChanged();
void cursorPositionChanged();
void roomChanged();
void chatBarCacheChanged();
void completionModelChanged();
void selectionStartChanged();
void selectionEndChanged();
void errorColorChanged();
@@ -154,10 +158,12 @@ Q_SIGNALS:
private:
int completionStartIndex() const;
bool m_isEdit = false;
QPointer<QQuickTextDocument> m_document;
QPointer<NeoChatRoom> m_room;
QPointer<ChatBarCache> m_chatBarCache;
bool completionVisible = false;
QColor m_mentionColor;
QColor m_errorColor;
@@ -171,5 +177,7 @@ private:
SyntaxHighlighter *m_highlighter = nullptr;
CompletionModel::AutoCompletionType m_completionType = CompletionModel::None;
CompletionModel *m_completionModel = nullptr;
};

View File

@@ -6,8 +6,13 @@
#include <qt6keychain/keychain.h>
#include <KConfig>
#include <KConfigGroup>
#include <KLocalizedString>
#include <KWindowConfig>
#ifdef HAVE_WINDOWSYSTEM
#include <KWindowEffects>
#endif
#include <QFile>
#include <QFileInfo>
@@ -42,8 +47,6 @@
#include "trayicon_sni.h"
#endif
bool testMode = false;
using namespace Quotient;
Controller::Controller(QObject *parent)
@@ -58,22 +61,11 @@ Controller::Controller(QObject *parent)
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
#endif
if (!testMode) {
QTimer::singleShot(0, this, [this] {
invokeLogin();
});
} else {
auto c = new NeoChatConnection(this);
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("token_1234"));
connect(c, &Connection::connected, this, [c, this]() {
m_accountRegistry.add(c);
c->syncLoop();
Q_EMIT initiated();
});
}
QTimer::singleShot(0, this, [this] {
invokeLogin();
});
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
delete m_trayIcon;
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
NeoChatConfig::self()->save();
});
@@ -105,30 +97,16 @@ Controller::Controller(QObject *parent)
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
if (m_accountRegistry.size() > oldAccountCount) {
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
connect(connection, &NeoChatConnection::syncDone, this, [this, connection]() {
connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
NotificationsManager::instance().handleNotifications(connection);
});
connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
connection->setupPushNotifications(m_endpoint);
});
}
oldAccountCount = m_accountRegistry.size();
});
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(QStringLiteral("org.kde.neochat"));
connect(connector, &KUnifiedPush::Connector::endpointChanged, this, [this](const QString &endpoint) {
m_endpoint = endpoint;
for (auto &quotientConnection : m_accountRegistry) {
auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
connection->setupPushNotifications(endpoint);
}
QTimer::singleShot(0, this, [this] {
m_pushRuleModel = new PushRuleModel;
});
connector->registerClient(i18n("Receiving push notifications"));
m_endpoint = connector->endpoint();
#endif
}
Controller &Controller::instance()
@@ -197,8 +175,6 @@ void Controller::invokeLogin()
QString id = NeoChatConfig::self()->activeConnection();
for (const auto &accountId : accounts) {
AccountSettings account{accountId};
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (id.isEmpty()) {
// handle case where the account config is empty
id = accountId;
@@ -218,8 +194,6 @@ void Controller::invokeLogin()
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
connection->loadState();
addConnection(connection);
m_accountsLoading.removeAll(connection->userId());
Q_EMIT accountsLoadingChanged();
if (connection->userId() == id) {
setActiveConnection(connection);
connectSingleShot(connection, &NeoChatConnection::syncDone, this, &Controller::initiated);
@@ -330,6 +304,9 @@ void Controller::setQuitOnLastWindowClosed()
m_trayIcon = nullptr;
}
}
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
#else
return;
#endif
}
@@ -353,6 +330,20 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
m_connection = connection;
if (connection != nullptr) {
NeoChatConfig::self()->setActiveConnection(connection->userId());
connect(connection, &NeoChatConnection::networkError, this, [this]() {
if (!m_isOnline) {
return;
}
m_isOnline = false;
Q_EMIT isOnlineChanged(false);
});
connect(connection, &NeoChatConnection::syncDone, this, [this] {
if (m_isOnline) {
return;
}
m_isOnline = true;
Q_EMIT isOnlineChanged(true);
});
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
@@ -363,6 +354,12 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
}
NeoChatConfig::self()->save();
Q_EMIT activeConnectionChanged();
Q_EMIT activeConnectionIndexChanged();
}
PushRuleModel *Controller::pushRuleModel() const
{
return m_pushRuleModel;
}
void Controller::saveWindowGeometry()
@@ -370,25 +367,79 @@ void Controller::saveWindowGeometry()
WindowController::instance().saveGeometry();
}
bool Controller::isOnline() const
{
return m_isOnline;
}
// TODO: Remove in favor of RoomManager::joinRoom
void Controller::joinRoom(const QString &alias)
{
if (!alias.contains(":"_ls)) {
Q_EMIT errorOccured(i18n("The room id you are trying to join is not valid"));
return;
}
const auto knownServer = alias.mid(alias.indexOf(":"_ls) + 1);
RoomManager::instance().joinRoom(m_connection, alias, QStringList{knownServer});
}
void Controller::openOrCreateDirectChat(User *user)
{
const auto existing = activeConnection()->directChats();
if (existing.contains(user)) {
const auto &room = static_cast<NeoChatRoom *>(activeConnection()->room(existing.value(user)));
if (room) {
RoomManager::instance().enterRoom(room);
return;
}
}
activeConnection()->requestDirectChat(user);
}
QString Controller::formatByteSize(double size, int precision) const
{
return QLocale().formattedDataSize(size, precision);
}
QString Controller::formatDuration(quint64 msecs, KFormat::DurationFormatOptions options) const
{
return KFormat().formatDuration(msecs, options);
}
void Controller::setBlur(QQuickItem *item, bool blur)
{
#ifdef HAVE_WINDOWSYSTEM
auto setWindows = [item, blur]() {
auto reg = QRect(QPoint(0, 0), item->window()->size());
KWindowEffects::enableBackgroundContrast(item->window(), blur, 1, 1, 1, reg);
KWindowEffects::enableBlurBehind(item->window(), blur, reg);
};
disconnect(item->window(), &QQuickWindow::heightChanged, this, nullptr);
disconnect(item->window(), &QQuickWindow::widthChanged, this, nullptr);
connect(item->window(), &QQuickWindow::heightChanged, this, setWindows);
connect(item->window(), &QQuickWindow::widthChanged, this, setWindows);
setWindows();
#endif
}
bool Controller::hasWindowSystem() const
{
#ifdef HAVE_WINDOWSYSTEM
return true;
#else
return false;
#endif
}
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
{
// HACK: Workaround bug QTBUG 93281
connect(textDocument->textDocument(), SIGNAL(imagesLoaded()), item, SLOT(updateWholeDocument()));
}
void Controller::listenForNotifications()
{
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(QStringLiteral("org.kde.neochat"));
connect(connector, &KUnifiedPush::Connector::messageReceived, [](const QByteArray &data) {
NotificationsManager::instance().postPushNotification(data);
});
connector->registerClient(i18n("Receiving push notifications"));
#endif
}
void Controller::setApplicationProxy()
{
NeoChatConfig *cfg = NeoChatConfig::self();
@@ -419,6 +470,14 @@ void Controller::setApplicationProxy()
QNetworkProxy::setApplicationProxy(proxy);
}
int Controller::activeConnectionIndex() const
{
auto result = std::find_if(m_accountRegistry.accounts().begin(), m_accountRegistry.accounts().end(), [this](const auto &it) {
return it == m_connection;
});
return result - m_accountRegistry.accounts().begin();
}
bool Controller::isFlatpak() const
{
#ifdef NEOCHAT_FLATPAK
@@ -434,8 +493,3 @@ AccountRegistry &Controller::accounts()
}
#include "moc_controller.cpp"
void Controller::setTestMode(bool test)
{
testMode = test;
}

View File

@@ -3,19 +3,18 @@
#pragma once
#include "models/pushrulemodel.h"
#include <QObject>
#include <QQmlEngine>
#include <QQuickItem>
#include <KFormat>
#include "neochatconnection.h"
#include <Quotient/accountregistry.h>
#include <Quotient/jobs/basejob.h>
#include <Quotient/settings.h>
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
class NeoChatRoom;
class TrayIcon;
class QQuickTextDocument;
@@ -50,11 +49,31 @@ class Controller : public QObject
*/
Q_PROPERTY(NeoChatConnection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
/**
* @brief The PushRuleModel that has the active connection's push rules.
*/
Q_PROPERTY(PushRuleModel *pushRuleModel READ pushRuleModel CONSTANT)
/**
* @brief The row number in the accounts directory of the active connection.
*/
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
/**
* @brief Whether the OS NeoChat is running on supports sytem tray icons.
*/
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
/**
* @brief Whether KWindowSystem specific features are available.
*/
Q_PROPERTY(bool hasWindowSystem READ hasWindowSystem CONSTANT)
/**
* @brief Whether NeoChat is currently able to connect to the server.
*/
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
/**
* @brief Whether NeoChat is running as a flatpak.
*
@@ -62,8 +81,6 @@ class Controller : public QObject
*/
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
public:
/**
* @brief Defines the status after an attempt to change the password on an account.
@@ -76,15 +93,16 @@ public:
Q_ENUM(PasswordStatus)
static Controller &instance();
static Controller *create(QQmlEngine *engine, QJSEngine *)
static Controller *create(QQmlEngine *, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
void setActiveConnection(NeoChatConnection *connection);
[[nodiscard]] NeoChatConnection *activeConnection() const;
[[nodiscard]] PushRuleModel *pushRuleModel() const;
/**
* @brief Add a new connection to the account registry.
*/
@@ -95,13 +113,34 @@ public:
*/
void dropConnection(NeoChatConnection *c);
int activeConnectionIndex() const;
/**
* @brief Save an access token to the keychain for the given account.
*/
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
/**
* @brief Join a room.
*/
Q_INVOKABLE void joinRoom(const QString &alias);
/**
* @brief Join a direct chat with the given user.
*
* If a direct chat with the user doesn't exist one is created and then joined.
*/
Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user);
[[nodiscard]] bool supportSystemTray() const;
/**
* @brief Set the background blur status of the given item.
*/
Q_INVOKABLE void setBlur(QQuickItem *item, bool blur);
bool isOnline() const;
/**
* @brief Sets the QNetworkProxy for the application.
*
@@ -111,6 +150,20 @@ public:
bool isFlatpak() const;
/**
* @brief Return a string for the input timestamp.
*
* The output format depends on the KFormat::DurationFormatOptions chosen.
*
* @sa KFormat::DurationFormatOptions
*/
Q_INVOKABLE QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const;
/**
* @brief Return a human readable string for a given input number of bytes.
*/
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
/**
* @brief Force a QQuickTextDocument to refresh when images are loaded.
*
@@ -118,16 +171,8 @@ public:
*/
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
/**
* @brief Start listening for notifications in dbus-activated mode.
* These notifications will quit the application when closed.
*/
static void listenForNotifications();
Quotient::AccountRegistry &accounts();
static void setTestMode(bool testMode);
private:
explicit Controller(QObject *parent = nullptr);
@@ -138,11 +183,13 @@ private:
void loadSettings();
void saveSettings() const;
bool m_isOnline = true;
QMap<Quotient::Room *, int> m_notificationCounts;
bool hasWindowSystem() const;
QPointer<PushRuleModel> m_pushRuleModel;
Quotient::AccountRegistry m_accountRegistry;
QStringList m_accountsLoading;
QString m_endpoint;
private Q_SLOTS:
void invokeLogin();
@@ -164,7 +211,8 @@ Q_SIGNALS:
void activeConnectionChanged();
void passwordStatus(Controller::PasswordStatus status);
void userConsentRequired(QUrl url);
void accountsLoadingChanged();
void isOnlineChanged(bool isOnline);
void activeConnectionIndexChanged();
public Q_SLOTS:
void saveWindowGeometry();

View File

@@ -40,8 +40,6 @@ public:
Poll, /**< The initial event for a poll. */
Location, /**< A location event. */
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
Loading, /**< A delegate to tell the user more messages are being loaded. */
TimelineEnd, /**< A delegate to inform that all messages are loaded. */
Other, /**< Anything that cannot be classified as another type. */
};
Q_ENUM(Type);

View File

@@ -19,7 +19,6 @@
#include <Quotient/events/stickerevent.h>
#include <Quotient/quotient_common.h>
#include "delegatetype.h"
#include "eventhandler_logging.h"
#include "events/pollevent.h"
#include "linkpreviewer.h"
@@ -51,10 +50,6 @@ const Quotient::Event *EventHandler::getEvent() const
void EventHandler::setEvent(const Quotient::RoomEvent *event)
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "cannot setEvent when m_room is set to nullptr.";
return;
}
if (event == m_event) {
return;
}
@@ -169,28 +164,6 @@ QString EventHandler::getAuthorDisplayName(bool isPending) const
}
}
QString EventHandler::singleLineAuthorDisplayname(bool isPending) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getAuthorDisplayName called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getAuthorDisplayName called with m_event set to nullptr.";
return {};
}
const auto author = isPending ? m_room->localUser() : m_room->user(m_event->senderId());
auto displayName = m_room->htmlSafeMemberName(author->id());
displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br>"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br />\n"), QStringLiteral(" "));
displayName.replace(QStringLiteral("<br />"), QStringLiteral(" "));
displayName.replace(u'\n', QStringLiteral(" "));
displayName.replace(u'\u2028', QStringLiteral(" "));
return displayName;
}
QDateTime EventHandler::getTime(bool isPending, QDateTime lastUpdated) const
{
if (m_event == nullptr) {
@@ -208,7 +181,7 @@ QDateTime EventHandler::getTime(bool isPending, QDateTime lastUpdated) const
QString EventHandler::getTimeString(bool relative, QLocale::FormatType format, bool isPending, QDateTime lastUpdated) const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "getTimeString called with m_event set to nullptr.";
qCWarning(EventHandling) << "getTime called with m_event set to nullptr.";
return {};
}
if (isPending && lastUpdated == QDateTime()) {
@@ -243,15 +216,6 @@ bool EventHandler::isHighlighted()
bool EventHandler::isHidden()
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "isHidden called with m_room set to nullptr.";
return false;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "isHidden called with m_event set to nullptr.";
return false;
}
if (m_event->isStateEvent() && !NeoChatConfig::self()->showStateEvent()) {
return true;
}
@@ -647,14 +611,6 @@ QString EventHandler::getGenericBody() const
QVariantMap EventHandler::getMediaInfo() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getMediaInfo called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getMediaInfo called with m_event set to nullptr.";
return {};
}
return getMediaInfoForEvent(m_event);
}
@@ -685,12 +641,12 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
QVariantMap mediaInfo;
// Get the mxc URL for the media.
if (!fileInfo->url().isValid() || fileInfo->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
if (!fileInfo->url().isValid() || eventId.isEmpty()) {
mediaInfo["source"_ls] = QUrl();
} else {
QUrl source = m_room->makeMediaUrl(eventId, fileInfo->url());
if (source.isValid()) {
if (source.isValid() && source.scheme() == QStringLiteral("mxc")) {
mediaInfo["source"_ls] = source;
} else {
mediaInfo["source"_ls] = QUrl();
@@ -767,14 +723,6 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
QSharedPointer<LinkPreviewer> EventHandler::getLinkPreviewer() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_room set to nullptr.";
return nullptr;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_event set to nullptr.";
return nullptr;
}
if (!m_event->is<RoomMessageEvent>()) {
return nullptr;
}
@@ -806,7 +754,7 @@ QSharedPointer<ReactionModel> EventHandler::getReactions() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReactions called with m_room set to nullptr.";
return nullptr;
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReactions called with m_event set to nullptr.";
@@ -858,33 +806,16 @@ QSharedPointer<ReactionModel> EventHandler::getReactions() const
bool EventHandler::hasReply() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "hasReply called with m_event set to nullptr.";
return false;
}
return !m_event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString().isEmpty();
}
QString EventHandler::getReplyId() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReplyId called with m_event set to nullptr.";
return {};
}
return m_event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
}
DelegateType::Type EventHandler::getReplyDelegateType() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReplyDelegateType called with m_room set to nullptr.";
return DelegateType::Other;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReplyDelegateType called with m_event set to nullptr.";
return DelegateType::Other;
}
auto replyEvent = m_room->getReplyForEvent(*m_event);
if (replyEvent == nullptr) {
return DelegateType::Other;
@@ -970,45 +901,8 @@ QVariantMap EventHandler::getReplyMediaInfo() const
return getMediaInfoForEvent(replyPtr);
}
bool EventHandler::isThreaded() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "isThreaded called with m_event set to nullptr.";
return false;
}
return (m_event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
&& m_event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls)
|| (!m_event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && m_event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls));
}
QString EventHandler::threadRoot() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "threadRoot called with m_event set to nullptr.";
return {};
}
// Get the thread root ID from m.relates_to if it exists.
if (m_event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
&& m_event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls) {
return m_event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString();
}
// For thread root events they have an m.relations in the unsigned part with a m.thread object.
// If so return the event ID as it is the root.
if (!m_event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && m_event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
return getId();
}
return {};
}
float EventHandler::getLatitude() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "getLatitude called with m_event set to nullptr.";
return -100.0;
}
const auto geoUri = m_event->contentJson()["geo_uri"_ls].toString();
if (geoUri.isEmpty()) {
return -100.0; // latitude runs from -90deg to +90deg so -100 is out of range.
@@ -1019,11 +913,6 @@ float EventHandler::getLatitude() const
float EventHandler::getLongitude() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "getLongitude called with m_event set to nullptr.";
return -200.0;
}
const auto geoUri = m_event->contentJson()["geo_uri"_ls].toString();
if (geoUri.isEmpty()) {
return -200.0; // longitude runs from -180deg to +180deg so -200 is out of range.
@@ -1034,11 +923,6 @@ float EventHandler::getLongitude() const
QString EventHandler::getLocationAssetType() const
{
if (m_event == nullptr) {
qCWarning(EventHandling) << "getLocationAssetType called with m_event set to nullptr.";
return {};
}
const auto assetType = m_event->contentJson()["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
if (assetType.isEmpty()) {
return {};
@@ -1048,15 +932,6 @@ QString EventHandler::getLocationAssetType() const
bool EventHandler::hasReadMarkers() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "hasReadMarkers called with m_room set to nullptr.";
return false;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "hasReadMarkers called with m_event set to nullptr.";
return false;
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
return userIds.size() > 0;
@@ -1064,15 +939,6 @@ bool EventHandler::hasReadMarkers() const
QVariantList EventHandler::getReadMarkers(int maxMarkers) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReadMarkers called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReadMarkers called with m_event set to nullptr.";
return {};
}
auto userIds_temp = m_room->userIdsAtEvent(m_event->id());
userIds_temp.remove(m_room->localUser()->id());
@@ -1093,15 +959,6 @@ QVariantList EventHandler::getReadMarkers(int maxMarkers) const
QString EventHandler::getNumberExcessReadMarkers(int maxMarkers) const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getNumberExcessReadMarkers called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getNumberExcessReadMarkers called with m_event set to nullptr.";
return {};
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
@@ -1114,15 +971,6 @@ QString EventHandler::getNumberExcessReadMarkers(int maxMarkers) const
QString EventHandler::getReadMarkersString() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getReadMarkersString called with m_room set to nullptr.";
return {};
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getReadMarkersString called with m_event set to nullptr.";
return {};
}
auto userIds = m_room->userIdsAtEvent(m_event->id());
userIds.remove(m_room->localUser()->id());
@@ -1138,5 +986,3 @@ QString EventHandler::getReadMarkersString() const
readMarkersString.chop(2);
return readMarkersString;
}
#include "moc_eventhandler.cpp"

View File

@@ -111,17 +111,6 @@ public:
*/
QString getAuthorDisplayName(bool isPending = false) const;
/**
* @brief Get the display name of the event author but with any newlines removed.
*
* Turns out you can put newlines in your display name so we need to handle that
* primarily for the room list subtitle.
*
* @param isPending whether the event is pending as this cannot be derived from
* just the event object.
*/
QString singleLineAuthorDisplayname(bool isPending = false) const;
/**
* @brief Return a QDateTime object for the event timestamp.
*/
@@ -337,20 +326,6 @@ public:
*/
QVariantMap getReplyMediaInfo() const;
/**
* @brief Whether the message is part of a thread.
*
* i.e. There is a rel_type of m.thread.
*/
bool isThreaded() const;
/**
* @brief Return the Matrix ID of the thread's root message.
*
* Empty if this not part of a thread.
*/
QString threadRoot() const;
/**
* @brief Return the latitude for the event.
*

View File

@@ -3,7 +3,7 @@
#pragma once
#include <QList>
#include <QVector>
#include <Quotient/events/eventcontent.h>
#include <Quotient/events/stateevent.h>
@@ -60,7 +60,7 @@ public:
*
* @sa ImagePackImage
*/
QList<ImagePackEventContent::ImagePackImage> images;
QVector<ImagePackEventContent::ImagePackImage> images;
explicit ImagePackEventContent(const QJsonObject &o);

View File

@@ -12,8 +12,7 @@ class LocationHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
QML_UNCREATABLE("")
public:
/** Unite two rectanlges. */
Q_INVOKABLE static QRectF unite(const QRectF &r1, const QRectF &r2);

View File

@@ -117,7 +117,7 @@ public:
file.write(buf.constData(), buf.size());
file.flush();
if (oldHandler && (!context.category || (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled))) {
if (oldHandler && (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled)) {
oldHandler(type, context, message);
}
}
@@ -197,6 +197,7 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt
break;
case QtFatalMsg:
sInstance()->log(QtInfoMsg, context, message);
abort();
}
}
@@ -211,7 +212,7 @@ void filter(QLoggingCategory *category)
void initLogging()
{
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtInfoMsg).isEnabled(QtDebugMsg);
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtDebugMsg).isEnabled(QtDebugMsg);
oldCategoryFilter = QLoggingCategory::installFilter(filter);
oldHandler = qInstallMessageHandler(messageHandler);
sInstance->setOrigHandler(oldHandler);

View File

@@ -100,8 +100,7 @@ void LoginHelper::init()
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
});
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
Q_EMIT loaded();
connectSingleShot(m_connection, &Connection::syncDone, this, []() {
Q_EMIT Controller::instance().initiated();
});
}

View File

@@ -79,17 +79,7 @@ class LoginHelper : public QObject
Q_PROPERTY(bool isInvalidPassword READ isInvalidPassword NOTIFY isInvalidPasswordChanged)
public:
static LoginHelper &instance()
{
static LoginHelper _instance;
return _instance;
}
static LoginHelper *create(QQmlEngine *engine, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
explicit LoginHelper(QObject *parent = nullptr);
Q_INVOKABLE void init();
@@ -135,7 +125,6 @@ Q_SIGNALS:
void isLoggingInChanged();
void isLoggedInChanged();
void isInvalidPasswordChanged();
void loaded();
private:
void setHomeserverReachable(bool reachable);
@@ -152,5 +141,4 @@ private:
bool m_isLoggingIn = false;
bool m_isLoggedIn = false;
bool m_invalidPassword = false;
explicit LoginHelper(QObject *parent = nullptr);
};

View File

@@ -34,21 +34,69 @@
#include "neochat-version.h"
#include <Quotient/accountregistry.h>
#include <Quotient/keyverificationsession.h>
#include <Quotient/networkaccessmanager.h>
#include <Quotient/room.h>
#include <Quotient/user.h>
#include <Quotient/util.h>
#include "actionshandler.h"
#include "blurhashimageprovider.h"
#include "colorschemer.h"
#include "chatdocumenthandler.h"
#include "controller.h"
#include "delegatesizehelper.h"
#include "enums/delegatetype.h"
#include "linkpreviewer.h"
#include "locationhelper.h"
#include "logger.h"
#include "login.h"
#include "matriximageprovider.h"
#include "mediasizehelper.h"
#include "models/accountemoticonmodel.h"
#include "models/customemojimodel.h"
#include "models/devicesmodel.h"
#include "models/devicesproxymodel.h"
#include "models/emojimodel.h"
#include "models/emoticonfiltermodel.h"
#include "models/imagepacksmodel.h"
#include "models/livelocationsmodel.h"
#include "models/locationsmodel.h"
#include "models/mediamessagefiltermodel.h"
#include "models/messageeventmodel.h"
#include "models/messagefiltermodel.h"
#include "models/publicroomlistmodel.h"
#include "models/pushrulemodel.h"
#include "models/reactionmodel.h"
#include "models/roomlistmodel.h"
#include "models/searchmodel.h"
#include "models/serverlistmodel.h"
#include "models/sortfilterroomlistmodel.h"
#include "models/sortfilterspacelistmodel.h"
#include "models/statefiltermodel.h"
#include "models/stickermodel.h"
#include "models/userdirectorylistmodel.h"
#include "models/userfiltermodel.h"
#include "models/userlistmodel.h"
#include "models/webshortcutmodel.h"
#include "neochatconfig.h"
#include "pollhandler.h"
#include "roommanager.h"
#include "spacehierarchycache.h"
#include "urlhelper.h"
#include "windowcontroller.h"
#ifdef HAVE_COLORSCHEME
#include "colorschemer.h"
#endif
#include "models/completionmodel.h"
#include "models/statemodel.h"
#ifdef HAVE_RUNNER
#include "runner.h"
#include <QDBusConnection>
#endif
#include "registration.h"
#ifdef Q_OS_WINDOWS
#include <Windows.h>
@@ -121,7 +169,7 @@ int main(int argc, char *argv[])
font.setHintingPreference(QFont::PreferNoHinting);
app.setFont(font);
#endif
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
KLocalizedString::setApplicationDomain("neochat");
QGuiApplication::setOrganizationName("KDE"_ls);
@@ -131,11 +179,7 @@ int main(int argc, char *argv[])
i18n("Matrix client"),
KAboutLicense::GPL_V3,
i18n("© 2018-2020 Black Hat, 2020-2023 KDE Community"));
about.addAuthor(i18n("Carl Schwan"),
i18n("Maintainer"),
QStringLiteral("carl@carlschwan.eu"),
QStringLiteral("https://carlschwan.eu"),
QStringLiteral("https://carlschwan.eu/avatar.png"));
about.addAuthor(i18n("Carl Schwan"), i18n("Maintainer"), QStringLiteral("carl@carlschwan.eu"), QStringLiteral("https://carlschwan.eu"));
about.addAuthor(i18n("Tobias Fella"), i18n("Maintainer"), QStringLiteral("tobias.fella@kde.org"), QStringLiteral("https://tobiasfella.de"));
about.addAuthor(i18n("James Graham"), i18n("Maintainer"), QStringLiteral("james.h.graham@protonmail.com"));
about.addCredit(i18n("Black Hat"), i18n("Original author of Spectral"), QStringLiteral("bhat@encom.eu.org"));
@@ -144,7 +188,7 @@ int main(int argc, char *argv[])
about.setOrganizationDomain("kde.org");
about.addComponent(QStringLiteral("libQuotient"),
i18n("A Qt library to write cross-platform clients for Matrix"),
i18n("A Qt5 library to write cross-platform clients for Matrix"),
i18nc("<version number> (built against <possibly different version number>)",
"%1 (built against %2)",
Quotient::versionString(),
@@ -157,7 +201,9 @@ int main(int argc, char *argv[])
initLogging();
#if Quotient_VERSION_MINOR == 8
Connection::setEncryptionDefault(true);
#endif
#ifdef NEOCHAT_FLATPAK
// Copy over the included FontConfig configuration to the
@@ -166,54 +212,23 @@ int main(int argc, char *argv[])
QStringLiteral("/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"));
#endif
#ifdef HAVE_COLORSCHEME
ColorSchemer colorScheme;
if (!NeoChatConfig::self()->colorScheme().isEmpty()) {
colorScheme.apply(NeoChatConfig::self()->colorScheme());
}
QCommandLineParser parser;
parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
parser.addPositionalArgument(QStringLiteral("urls"), i18n("Supports matrix: url scheme"));
parser.addOption(QCommandLineOption("ignore-ssl-errors"_ls, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
QCommandLineOption testOption("test"_ls, i18n("Only used for autotests"));
testOption.setFlags(QCommandLineOption::HiddenFromHelp);
parser.addOption(testOption);
#ifdef HAVE_KUNIFIEDPUSH
QCommandLineOption dbusActivatedOption(QStringLiteral("dbus-activated"), i18n("Internal usage only."));
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
parser.addOption(dbusActivatedOption);
#endif
about.setupCommandLine(&parser);
parser.process(app);
about.processCommandLine(&parser);
Controller::setTestMode(parser.isSet("test"_ls));
#ifdef HAVE_KUNIFIEDPUSH
if (parser.isSet(dbusActivatedOption)) {
// We want to be replaceable by the main client
KDBusService service(KDBusService::Replace);
Controller::listenForNotifications();
return QCoreApplication::exec();
}
#endif
#ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique);
#endif
qml_register_types_org_kde_neochat();
qmlRegisterSingletonInstance("org.kde.neochat.config", 1, 0, "Config", NeoChatConfig::self());
qmlRegisterSingletonInstance("org.kde.neochat.accounts", 1, 0, "AccountRegistry", &Controller::instance().accounts());
qmlRegisterUncreatableType<KeyVerificationSession>("com.github.quotient_im.libquotient", 1, 0, "KeyVerificationSession", {});
// qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
QQmlApplicationEngine engine;
#ifdef HAVE_KDBUSADDONS
KDBusService service(KDBusService::Unique);
service.connect(&service,
&KDBusService::activateRequested,
&RoomManager::instance(),
@@ -232,7 +247,7 @@ int main(int argc, char *argv[])
auto args = arguments;
args.removeFirst();
for (const auto &arg : args) {
RoomManager::instance().resolveResource(arg);
RoomManager::instance().openResource(arg);
}
});
#endif
@@ -241,13 +256,22 @@ int main(int argc, char *argv[])
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
QCommandLineParser parser;
parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
parser.addPositionalArgument(QStringLiteral("urls"), i18n("Supports matrix: url scheme"));
parser.addOption(QCommandLineOption("ignore-ssl-errors"_ls, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
about.setupCommandLine(&parser);
parser.process(app);
about.processCommandLine(&parser);
if (parser.isSet("ignore-ssl-errors"_ls)) {
QObject::connect(NetworkAccessManager::instance(), &QNetworkAccessManager::sslErrors, NetworkAccessManager::instance(), [](QNetworkReply *reply) {
reply->ignoreSslErrors();
});
}
engine.addImageProvider(QLatin1String("mxc"), MatrixImageProvider::create(&engine, &engine));
engine.addImageProvider(QLatin1String("mxc"), new MatrixImageProvider);
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
engine.load(QUrl(QStringLiteral("qrc:/org/kde/neochat/qml/main.qml")));
@@ -260,8 +284,8 @@ int main(int argc, char *argv[])
}
#ifdef HAVE_RUNNER
auto runner = Runner::create(&engine, &engine);
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, runner, QDBusConnection::ExportScriptableContents);
Runner runner;
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, &runner, QDBusConnection::ExportScriptableContents);
#endif
QWindow *window = windowFromEngine(&engine);

View File

@@ -13,13 +13,11 @@
#include <KLocalizedString>
#include "controller.h"
#include "neochatconnection.h"
#include <Quotient/connection.h>
using namespace Quotient;
ThumbnailResponse::ThumbnailResponse(QString id, QSize size, NeoChatConnection *connection)
ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
: mediaId(std::move(id))
, requestedSize(size)
, localFile(QStringLiteral("%1/image_provider/%2-%3x%4.png")
@@ -27,7 +25,6 @@ ThumbnailResponse::ThumbnailResponse(QString id, QSize size, NeoChatConnection *
mediaId,
QString::number(requestedSize.width()),
QString::number(requestedSize.height())))
, m_connection(connection)
, errorStr("Image request hasn't started"_ls)
{
if (requestedSize.isEmpty()) {
@@ -54,24 +51,24 @@ ThumbnailResponse::ThumbnailResponse(QString id, QSize size, NeoChatConnection *
return;
}
if (!m_connection) {
if (!Controller::instance().activeConnection()) {
qWarning() << "Current connection is null";
return;
}
// Execute a request on the main thread asynchronously
moveToThread(m_connection->thread());
moveToThread(Controller::instance().activeConnection()->thread());
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, Qt::QueuedConnection);
}
void ThumbnailResponse::startRequest()
{
if (!m_connection) {
if (!Controller::instance().activeConnection()) {
return;
}
// Runs in the main thread, not QML thread
Q_ASSERT(QThread::currentThread() == m_connection->thread());
job = m_connection->getThumbnail(mediaId, requestedSize);
Q_ASSERT(QThread::currentThread() == Controller::instance().activeConnection()->thread());
job = Controller::instance().activeConnection()->getThumbnail(mediaId, requestedSize);
// Connect to any possible outcome including abandonment
// to make sure the QML thread is not left stuck forever.
connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
@@ -121,7 +118,7 @@ QString ThumbnailResponse::errorString() const
QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
{
return new ThumbnailResponse(id, requestedSize, m_connection);
return new ThumbnailResponse(id, requestedSize);
}
#include "moc_matriximageprovider.cpp"

View File

@@ -10,8 +10,6 @@
#include <QReadWriteLock>
class NeoChatConnection;
/**
* @class ThumbnailResponse
*
@@ -23,7 +21,7 @@ class ThumbnailResponse : public QQuickImageResponse
{
Q_OBJECT
public:
explicit ThumbnailResponse(QString mediaId, QSize requestedSize, NeoChatConnection *m_connection);
explicit ThumbnailResponse(QString mediaId, QSize requestedSize);
~ThumbnailResponse() override = default;
private Q_SLOTS:
@@ -35,7 +33,6 @@ private:
QSize requestedSize;
const QString localFile;
Quotient::MediaThumbnailJob *job = nullptr;
NeoChatConnection *m_connection;
QImage image;
QString errorStr;
@@ -54,27 +51,11 @@ private:
*/
class MatrixImageProvider : public QQuickAsyncImageProvider
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(NeoChatConnection *connection MEMBER m_connection)
public:
static MatrixImageProvider *create(QQmlEngine *engine, QJSEngine *)
{
static MatrixImageProvider *instance = new MatrixImageProvider;
engine->setObjectOwnership(instance, QQmlEngine::CppOwnership);
return instance;
}
/**
* @brief Return a job to provide the image with the given ID.
*
* @sa QQuickAsyncImageProvider::requestImageResponse
*/
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
private:
NeoChatConnection *m_connection = nullptr;
MatrixImageProvider() = default;
};

View File

@@ -7,10 +7,10 @@
#include <QAbstractListModel>
#include <QCoroTask>
#include <QList>
#include <QObject>
#include <QPointer>
#include <QQmlEngine>
#include <QVector>
#include <Quotient/connection.h>

View File

@@ -3,7 +3,6 @@
#include "actionsmodel.h"
#include "chatbarcache.h"
#include "controller.h"
#include "neochatroom.h"
#include "roommanager.h"
@@ -20,7 +19,7 @@ QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls
"#00d4ff"_ls, "#00aaff"_ls, "#007fff"_ls, "#0055ff"_ls, "#002bff"_ls, "#0000ff"_ls, "#2a00ff"_ls, "#5500ff"_ls, "#7f00ff"_ls,
"#aa00ff"_ls, "#d400ff"_ls, "#ff00ff"_ls, "#ff00d4"_ls, "#ff00aa"_ls, "#ff0080"_ls, "#ff0055"_ls, "#ff002b"_ls, "#ff0000"_ls};
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
room->connection()->leaveRoom(room);
@@ -46,7 +45,7 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
return QString();
};
auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
auto roomNickLambda = [](const QString &text, NeoChatRoom *room) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
} else {
@@ -55,10 +54,10 @@ auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *)
return QString();
};
QList<ActionsModel::Action> actions{
QVector<ActionsModel::Action> actions{
Action{
QStringLiteral("shrug"),
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
[](const QString &message, NeoChatRoom *) {
return QStringLiteral("¯\\\\_(ツ)_/¯ %1").arg(message);
},
true,
@@ -68,7 +67,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("lenny"),
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
[](const QString &message, NeoChatRoom *) {
return QStringLiteral("( ͡° ͜ʖ ͡°) %1").arg(message);
},
true,
@@ -78,7 +77,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("tableflip"),
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
[](const QString &message, NeoChatRoom *) {
return QStringLiteral("(╯°□°)╯︵ ┻━┻ %1").arg(message);
},
true,
@@ -88,7 +87,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unflip"),
[](const QString &message, NeoChatRoom *, ChatBarCache *) {
[](const QString &message, NeoChatRoom *) {
return QStringLiteral("┬──┬ ( ゜-゜ノ) %1").arg(message);
},
true,
@@ -98,7 +97,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("rainbow"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
[](const QString &text, NeoChatRoom *room) {
QString rainbowText;
for (int i = 0; i < text.length(); i++) {
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
@@ -107,8 +106,8 @@ QList<ActionsModel::Action> actions{
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Text,
chatBarCache->replyId(),
chatBarCache->editId());
room->chatBoxReplyId(),
room->chatBoxEditId());
return QString();
},
false,
@@ -118,7 +117,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("rainbowme"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
[](const QString &text, NeoChatRoom *room) {
QString rainbowText;
for (int i = 0; i < text.length(); i++) {
rainbowText += QStringLiteral("<font color='%2'>%3</font>").arg(rainbowColors[i % rainbowColors.length()], text.at(i));
@@ -127,8 +126,8 @@ QList<ActionsModel::Action> actions{
room->postMessage(QStringLiteral("/rainbow %1").arg(text),
rainbowText,
RoomMessageEvent::MsgType::Emote,
chatBarCache->replyId(),
chatBarCache->editId());
room->chatBoxReplyId(),
room->chatBoxEditId());
return QString();
},
false,
@@ -138,7 +137,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("plain"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
room->postMessage(text, text.toHtmlEscaped(), RoomMessageEvent::MsgType::Text, {}, {});
return QString();
},
@@ -149,13 +148,13 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("spoiler"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
[](const QString &text, NeoChatRoom *room) {
// Ideally, we would just return rainbowText and let that do the rest, but the colors don't survive markdownToHTML.
room->postMessage(QStringLiteral("/spoiler %1").arg(text),
QStringLiteral("<span data-mx-spoiler>%1</span>").arg(text),
RoomMessageEvent::MsgType::Text,
chatBarCache->replyId(),
chatBarCache->editId());
room->chatBoxReplyId(),
room->chatBoxEditId());
return QString();
},
false,
@@ -165,7 +164,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("me"),
[](const QString &text, NeoChatRoom *, ChatBarCache *) {
[](const QString &text, NeoChatRoom *) {
return text;
},
true,
@@ -175,7 +174,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("notice"),
[](const QString &text, NeoChatRoom *, ChatBarCache *) {
[](const QString &text, NeoChatRoom *) {
return text;
},
true,
@@ -185,7 +184,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("invite"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -221,7 +220,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("join"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
@@ -235,7 +234,7 @@ QList<ActionsModel::Action> actions{
return QString();
}
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
RoomManager::instance().resolveResource(text, "join"_ls);
Controller::instance().joinRoom(text);
return QString();
},
false,
@@ -245,7 +244,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("knock"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
auto parts = text.split(QLatin1String(" "));
QString roomName = parts[0];
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
@@ -261,7 +260,7 @@ QList<ActionsModel::Action> actions{
return QString();
}
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
auto connection = room->connection();
auto connection = Controller::instance().activeConnection();
const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
if (parts.length() >= 2) {
RoomManager::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
@@ -277,7 +276,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("j"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
auto regexMatch = roomRegex.match(text);
if (!regexMatch.hasMatch()) {
@@ -285,12 +284,12 @@ QList<ActionsModel::Action> actions{
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
return QString();
}
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
if (Controller::instance().activeConnection()->room(text) || Controller::instance().activeConnection()->roomByAlias(text)) {
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
return QString();
}
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
RoomManager::instance().resolveResource(text, "join"_ls);
Controller::instance().joinRoom(text);
return QString();
},
false,
@@ -316,7 +315,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("nick"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
if (text.isEmpty()) {
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
} else {
@@ -347,7 +346,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("ignore"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -375,7 +374,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unignore"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -403,8 +402,9 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("react"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *chatBarCache) {
if (chatBarCache->replyId().isEmpty()) {
[](const QString &text, NeoChatRoom *room) {
QString replyEventId = room->chatBoxReplyId();
if (replyEventId.isEmpty()) {
for (auto it = room->messageEvents().crbegin(); it != room->messageEvents().crend(); it++) {
const auto &evt = **it;
if (const auto event = eventCast<const RoomMessageEvent>(&evt)) {
@@ -413,7 +413,7 @@ QList<ActionsModel::Action> actions{
}
}
}
room->toggleReaction(chatBarCache->replyId(), text);
room->toggleReaction(replyEventId, text);
return QString();
},
false,
@@ -423,7 +423,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("ban"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
auto parts = text.split(QLatin1String(" "));
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
@@ -462,7 +462,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("unban"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
auto regexMatch = mxidRegex.match(text);
@@ -495,7 +495,7 @@ QList<ActionsModel::Action> actions{
},
Action{
QStringLiteral("kick"),
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
[](const QString &text, NeoChatRoom *room) {
auto parts = text.split(QLatin1String(" "));
static const QRegularExpression mxidRegex(
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
@@ -574,7 +574,7 @@ QHash<int, QByteArray> ActionsModel::roleNames() const
};
}
QList<Action> &ActionsModel::allActions() const
QVector<Action> &ActionsModel::allActions() const
{
return actions;
}

View File

@@ -7,7 +7,6 @@
#include <QAbstractListModel>
#include <Quotient/events/roommessageevent.h>
class ChatBarCache;
class NeoChatRoom;
/**
@@ -29,7 +28,7 @@ public:
/**
* @brief The function to execute when the action is triggered.
*/
std::function<QString(const QString &, NeoChatRoom *, ChatBarCache *)> handle;
std::function<QString(const QString &, NeoChatRoom *)> handle;
/**
* @brief Whether the action is a message type action.
*
@@ -88,7 +87,7 @@ public:
/**
* @brief Return a vector with all supported actions.
*/
QList<Action> &allActions() const;
QVector<Action> &allActions() const;
private:
ActionsModel() = default;

View File

@@ -15,28 +15,13 @@
using namespace Quotient;
void CustomEmojiModel::setConnection(NeoChatConnection *connection)
{
if (connection == m_connection) {
return;
}
m_connection = connection;
Q_EMIT connectionChanged();
fetchEmojis();
}
NeoChatConnection *CustomEmojiModel::connection() const
{
return m_connection;
}
void CustomEmojiModel::fetchEmojis()
{
if (!m_connection) {
if (!Controller::instance().activeConnection()) {
return;
}
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
if (data == nullptr) {
return;
}
@@ -68,10 +53,10 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
{
using namespace Quotient;
auto job = m_connection->uploadFile(location.toLocalFile());
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
connect(job, &BaseJob::success, this, [name, location, job, this] {
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
connect(job, &BaseJob::success, this, [name, location, job] {
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
auto json = data != nullptr ? data->contentJson() : QJsonObject();
auto emojiData = json["images"_ls].toObject();
@@ -93,7 +78,7 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
});
json["images"_ls] = emojiData;
m_connection->setAccountData("im.ponies.user_emotes"_ls, json);
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes"_ls, json);
});
}
@@ -101,7 +86,7 @@ void CustomEmojiModel::removeEmoji(const QString &name)
{
using namespace Quotient;
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
Q_ASSERT(data);
auto json = data->contentJson();
const QString _name = name.mid(1).chopped(1);
@@ -124,25 +109,24 @@ void CustomEmojiModel::removeEmoji(const QString &name)
emojiData.remove(_name);
json["emoticons"_ls] = emojiData;
}
m_connection->setAccountData("im.ponies.user_emotes"_ls, json);
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes"_ls, json);
}
CustomEmojiModel::CustomEmojiModel(QObject *parent)
: QAbstractListModel(parent)
{
connect(this, &CustomEmojiModel::connectionChanged, this, [this]() {
if (!m_connection) {
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
if (!Controller::instance().activeConnection()) {
return;
}
CustomEmojiModel::fetchEmojis();
connect(m_connection, &Connection::accountDataChanged, this, [this](const QString &id) {
connect(Controller::instance().activeConnection(), &Connection::accountDataChanged, this, [this](const QString &id) {
if (id != QStringLiteral("im.ponies.user_emotes")) {
return;
}
fetchEmojis();
});
});
CustomEmojiModel::fetchEmojis();
}
QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const

View File

@@ -8,8 +8,6 @@
#include <QRegularExpression>
#include <memory>
class NeoChatConnection;
struct CustomEmoji {
QString name; // with :semicolons:
QString url; // mxc://
@@ -33,8 +31,6 @@ class CustomEmojiModel : public QAbstractListModel
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
public:
/**
* @brief Defines the model roles.
@@ -55,9 +51,8 @@ public:
static CustomEmojiModel _instance;
return _instance;
}
static CustomEmojiModel *create(QQmlEngine *engine, QJSEngine *)
static CustomEmojiModel *create(QQmlEngine *, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
@@ -102,16 +97,9 @@ public:
*/
Q_INVOKABLE void removeEmoji(const QString &name);
void setConnection(NeoChatConnection *connection);
NeoChatConnection *connection() const;
Q_SIGNALS:
void connectionChanged();
private:
explicit CustomEmojiModel(QObject *parent = nullptr);
QList<CustomEmoji> m_emojis;
NeoChatConnection *m_connection = nullptr;
void fetchEmojis();
};

View File

@@ -24,8 +24,8 @@ DevicesModel::DevicesModel(QObject *parent)
void DevicesModel::fetchDevices()
{
if (m_connection) {
auto job = m_connection->callApi<GetDevicesJob>();
if (Controller::instance().activeConnection()) {
auto job = Controller::instance().activeConnection()->callApi<GetDevicesJob>();
connect(job, &BaseJob::success, this, [this, job]() {
beginResetModel();
m_devices = job->devices();
@@ -102,7 +102,7 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
for (index = 0; m_devices[index].deviceId != deviceId; index++)
;
auto job = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
auto job = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
connect(job, &BaseJob::result, this, [this, job, password, index] {
auto onSuccess = [this, index]() {
@@ -117,9 +117,9 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
authData["session"_ls] = replyData["session"_ls];
authData["password"_ls] = password;
authData["type"_ls] = "m.login.password"_ls;
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, Controller::instance().activeConnection()->user()->id()}};
authData["identifier"_ls] = identifier;
auto *innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
connect(innerJob, &BaseJob::success, this, onSuccess);
} else {
onSuccess();
@@ -132,7 +132,7 @@ void DevicesModel::setName(const QString &deviceId, const QString &name)
int index;
for (index = 0; m_devices[index].deviceId != deviceId; index++);
auto job = m_connection->callApi<UpdateDeviceJob>(m_devices[index].deviceId, name);
auto job = Controller::instance().activeConnection()->callApi<UpdateDeviceJob>(m_devices[index].deviceId, name);
QString oldName = m_devices[index].displayName;
beginResetModel();
m_devices[index].displayName = name;
@@ -160,7 +160,7 @@ void DevicesModel::setConnection(Connection *connection)
connect(m_connection, &Connection::sessionVerified, this, [this](const QString &userId, const QString &deviceId) {
Q_UNUSED(deviceId);
if (userId == m_connection->userId()) {
if (userId == Controller::instance().activeConnection()->userId()) {
fetchDevices();
}
});

View File

@@ -98,6 +98,6 @@ Q_SIGNALS:
private:
void fetchDevices();
QList<Quotient::Device> m_devices;
QVector<Quotient::Device> m_devices;
QPointer<Quotient::Connection> m_connection;
};

View File

@@ -14,8 +14,6 @@
EmojiModel::EmojiModel(QObject *parent)
: QAbstractListModel(parent)
, m_config(KSharedConfig::openStateConfig())
, m_configGroup(KConfigGroup(m_config, QStringLiteral("Editor")))
{
if (_emojis.isEmpty()) {
#include "emojis.h"
@@ -63,9 +61,9 @@ QHash<int, QByteArray> EmojiModel::roleNames() const
return {{ShortNameRole, "shortName"}, {UnicodeRole, "unicode"}};
}
QStringList EmojiModel::lastUsedEmojis() const
QVariantList EmojiModel::history() const
{
return m_configGroup.readEntry(QStringLiteral("lastUsedEmojis"), QStringList());
return m_settings.value(QStringLiteral("Editor/emojis"), QVariantList()).toList();
}
QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
@@ -95,21 +93,19 @@ QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
void EmojiModel::emojiUsed(const QVariant &modelData)
{
auto list = lastUsedEmojis();
const auto emoji = modelData.value<Emoji>();
QVariantList list = history();
auto it = list.begin();
while (it != list.end()) {
if (*it == emoji.shortName) {
if ((*it).value<Emoji>().unicode == modelData.value<Emoji>().unicode) {
it = list.erase(it);
} else {
it++;
}
}
list.push_front(emoji.shortName);
m_configGroup.writeEntry(QStringLiteral("lastUsedEmojis"), list);
list.push_front(modelData);
m_settings.setValue(QStringLiteral("Editor/emojis"), list);
Q_EMIT historyChanged();
}
@@ -117,11 +113,11 @@ void EmojiModel::emojiUsed(const QVariant &modelData)
QVariantList EmojiModel::emojis(Category category) const
{
if (category == History) {
return emojiHistory();
return history();
}
if (category == HistoryNoCustom) {
QVariantList list;
for (const auto &e : emojiHistory()) {
for (const auto &e : history()) {
auto emoji = qvariant_cast<Emoji>(e);
if (!emoji.isCustom) {
list.append(e);
@@ -221,19 +217,4 @@ QVariantList EmojiModel::categoriesWithCustom() const
return cats;
}
QVariantList EmojiModel::emojiHistory() const
{
QVariantList list;
for (const auto &historicEmoji : lastUsedEmojis()) {
for (const auto &emojiCategory : _emojis) {
for (const auto &emoji : emojiCategory) {
if (qvariant_cast<Emoji>(emoji).shortName == historicEmoji) {
list.append(emoji);
}
}
}
}
return list;
}
#include "moc_emojimodel.cpp"

View File

@@ -3,11 +3,10 @@
#pragma once
#include <KConfigGroup>
#include <KSharedConfig>
#include <QAbstractListModel>
#include <QObject>
#include <QQmlEngine>
#include <QSettings>
struct Emoji {
Emoji(QString unicode, QString shortname, bool isCustom = false)
@@ -24,6 +23,21 @@ struct Emoji {
}
Emoji() = default;
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
{
arch << object.unicode;
arch << object.shortName;
return arch;
}
friend QDataStream &operator>>(QDataStream &arch, Emoji &object)
{
arch >> object.unicode;
arch >> object.shortName;
object.isCustom = object.unicode.startsWith(QStringLiteral("image://"));
return arch;
}
QString unicode;
QString shortName;
QString description;
@@ -49,6 +63,11 @@ class EmojiModel : public QAbstractListModel
QML_ELEMENT
QML_SINGLETON
/**
* @brief Return a list of recently used emojis.
*/
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
/**
* @brief Return a list of emoji categories.
*
@@ -67,9 +86,8 @@ public:
static EmojiModel _instance;
return _instance;
}
static EmojiModel *create(QQmlEngine *engine, QJSEngine *)
static EmojiModel *create(QQmlEngine *, QJSEngine *)
{
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
return &instance();
}
@@ -158,11 +176,7 @@ public:
*/
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
/**
* @brief Return a list of the last used emoji shortnames
*/
QStringList lastUsedEmojis() const;
Q_INVOKABLE QVariantList history() const;
QVariantList categories() const;
QVariantList categoriesWithCustom() const;
@@ -175,10 +189,7 @@ public Q_SLOTS:
private:
static QHash<Category, QVariantList> _emojis;
/// Returns QVariants containing the last used Emojis
QVariantList emojiHistory() const;
KSharedConfig::Ptr m_config;
KConfigGroup m_configGroup;
// TODO: Port away from QSettings
QSettings m_settings;
EmojiModel(QObject *parent = nullptr);
};

View File

@@ -102,9 +102,6 @@ void ImagePacksModel::reloadImages()
}
auto packs = rooms[roomId].toObject();
const auto &stickerRoom = m_room->connection()->room(roomId);
if (!stickerRoom) {
continue;
}
for (const auto &packKey : packs.keys()) {
if (const auto &pack = stickerRoom->currentState().get<ImagePackEvent>(packKey)) {
const auto packContent = pack->content();
@@ -154,7 +151,7 @@ void ImagePacksModel::setShowEmoticons(bool showEmoticons)
m_showEmoticons = showEmoticons;
Q_EMIT showEmoticonsChanged();
}
QList<Quotient::ImagePackEventContent::ImagePackImage> ImagePacksModel::images(int index)
QVector<Quotient::ImagePackEventContent::ImagePackImage> ImagePacksModel::images(int index)
{
if (index < 0 || index >= m_events.size()) {
return {};

View File

@@ -5,9 +5,9 @@
#include "events/imagepackevent.h"
#include <QAbstractListModel>
#include <QList>
#include <QPointer>
#include <QQmlEngine>
#include <QVector>
class NeoChatRoom;
@@ -86,7 +86,7 @@ public:
/**
* @brief Return a vector of the images in the pack at the given index.
*/
[[nodiscard]] QList<Quotient::ImagePackEventContent::ImagePackImage> images(int index);
[[nodiscard]] QVector<Quotient::ImagePackEventContent::ImagePackImage> images(int index);
Q_SIGNALS:
void roomChanged();
@@ -96,7 +96,7 @@ Q_SIGNALS:
private:
QPointer<NeoChatRoom> m_room;
QList<Quotient::ImagePackEventContent> m_events;
QVector<Quotient::ImagePackEventContent> m_events;
bool m_showStickers = true;
bool m_showEmoticons = true;
void reloadImages();

Some files were not shown because too many files have changed in this diff Show More