Compare commits
235 Commits
work/nvrwh
...
v24.01.85
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0b82aeb5cc | ||
|
|
bc7aa4e272 | ||
|
|
d1f421994d | ||
|
|
b043cbedc9 | ||
|
|
5a469ed126 | ||
|
|
8d209a5424 | ||
|
|
a0937bdcac | ||
|
|
df4eba0191 | ||
|
|
9f83cf1c52 | ||
|
|
ddc0bfe786 | ||
|
|
09796b6e0b | ||
|
|
73c34c68b7 | ||
|
|
76ab96ef3e | ||
|
|
5a580dd16c | ||
|
|
918887525b | ||
|
|
500ebbecbf | ||
|
|
8062dface6 | ||
|
|
8d717b78ac | ||
|
|
4751ac6acc | ||
|
|
1d8ffb22ee | ||
|
|
a035d6542d | ||
|
|
d7230022f6 | ||
|
|
d25340bc31 | ||
|
|
c396024a26 | ||
|
|
65b94890ab | ||
|
|
682004d9a2 | ||
|
|
decf797180 | ||
|
|
552b6dd913 | ||
|
|
e6358eca16 | ||
|
|
f3bacad460 | ||
|
|
862f4363a9 | ||
|
|
9b3a3dc562 | ||
|
|
c2641a5fc2 | ||
|
|
1213ba5916 | ||
|
|
0521ff2c4d | ||
|
|
65c892787e | ||
|
|
7541c192bf | ||
|
|
c692d7e679 | ||
|
|
44a9a491af | ||
|
|
f8cae7c143 | ||
|
|
4f2b559126 | ||
|
|
46fd288a95 | ||
|
|
3a1cac6c99 | ||
|
|
225092a193 | ||
|
|
2fcb17621c | ||
|
|
69e3ee862f | ||
|
|
47c12cb582 | ||
|
|
94cf75af68 | ||
|
|
259b9884c7 | ||
|
|
2980dc49e4 | ||
|
|
5e12f50899 | ||
|
|
79940f707f | ||
|
|
e7024a270c | ||
|
|
1a561f6649 | ||
|
|
2cbb6ed65c | ||
|
|
ab4639926a | ||
|
|
89b6c54f25 | ||
|
|
27c9c62564 | ||
|
|
bb8ffb02d1 | ||
|
|
201aa82c04 | ||
|
|
704430db7a | ||
|
|
dcbbbd9296 | ||
|
|
410258c478 | ||
|
|
6baf2e4888 | ||
|
|
dd6eaac556 | ||
|
|
840903128c | ||
|
|
68f0ca96da | ||
|
|
faf7af06fe | ||
|
|
4ef67c3e0d | ||
|
|
5efd17d370 | ||
|
|
0dbef58ff2 | ||
|
|
6a3df8baf4 | ||
|
|
2fc973f218 | ||
|
|
7d16999c44 | ||
|
|
f9ba31f2dc | ||
|
|
d4ad773ff1 | ||
|
|
822a4dc500 | ||
|
|
bfd1d431c1 | ||
|
|
94bf2481f0 | ||
|
|
5c32520c35 | ||
|
|
de47597f6e | ||
|
|
7780a72888 | ||
|
|
1f8c07fedf | ||
|
|
877575b4d3 | ||
|
|
1181df4db2 | ||
|
|
5be2113b32 | ||
|
|
7ad7af56e8 | ||
|
|
d3148f8c8b | ||
|
|
100f595026 | ||
|
|
15ba6d58e2 | ||
|
|
61ad892732 | ||
|
|
2a3e1dfcd7 | ||
|
|
d1dc6fc4ed | ||
|
|
6dc30a9ca7 | ||
|
|
ae0c5ffaef | ||
|
|
fc546d4a43 | ||
|
|
96e62e3ebe | ||
|
|
d979cd2fbc | ||
|
|
22298181cb | ||
|
|
1312fde470 | ||
|
|
7fe2feb1e4 | ||
|
|
67fb5d0824 | ||
|
|
359114bd3d | ||
|
|
3db9f1198b | ||
|
|
2435a6b953 | ||
|
|
e6c8b3fa4b | ||
|
|
2d55dca508 | ||
|
|
bbb0bc3092 | ||
|
|
4065aa6a2e | ||
|
|
5942eac5ed | ||
|
|
85c2b7dada | ||
|
|
86ef921cdb | ||
|
|
aab69c5bae | ||
|
|
624578ec77 | ||
|
|
197ff984fd | ||
|
|
a26337d5f4 | ||
|
|
31b4eefadd | ||
|
|
b8e592f8ba | ||
|
|
555d23863e | ||
|
|
ffa2d5dc0e | ||
|
|
9987edbaf2 | ||
|
|
d90298392d | ||
|
|
600dbd0603 | ||
|
|
2179e2cc35 | ||
|
|
8119ea3ccb | ||
|
|
3fc125a798 | ||
|
|
99f6778df4 | ||
|
|
f7bd24db34 | ||
|
|
41c2f9c4d5 | ||
|
|
56d5f3036b | ||
|
|
21bb7dce21 | ||
|
|
11081719a7 | ||
|
|
980de7d85d | ||
|
|
7735313b0c | ||
|
|
1a3055df86 | ||
|
|
84cad630cd | ||
|
|
0beb5df08d | ||
|
|
4ef44b8e93 | ||
|
|
59153be006 | ||
|
|
50be96f762 | ||
|
|
819d88e18c | ||
|
|
dbbad2cf13 | ||
|
|
08b84c6592 | ||
|
|
b8abf0540d | ||
|
|
7f9e709559 | ||
|
|
c4f6abee9d | ||
|
|
1a4947b98a | ||
|
|
6ba2b715c3 | ||
|
|
e9e1e223f7 | ||
|
|
006da1fb16 | ||
|
|
3aff1795c8 | ||
|
|
576b1f928f | ||
|
|
fc3ab50701 | ||
|
|
e7fa3ad524 | ||
|
|
5adffddbd8 | ||
|
|
1d95d5aa15 | ||
|
|
dabd6291a5 | ||
|
|
0e55c3b38f | ||
|
|
ff4cf86ea5 | ||
|
|
f4d5ccbf12 | ||
|
|
192601d358 | ||
|
|
d7c432119e | ||
|
|
eddb2b73c2 | ||
|
|
0c60bfdb83 | ||
|
|
2bfb2fa1f9 | ||
|
|
d1aac971bf | ||
|
|
284cadf305 | ||
|
|
4697b7fcf1 | ||
|
|
9f356912c9 | ||
|
|
1cf891f845 | ||
|
|
52e2d636b9 | ||
|
|
dc6b539ddf | ||
|
|
e3cf85aa8c | ||
|
|
2065eb6684 | ||
|
|
9cac2a8abd | ||
|
|
feb87e6f70 | ||
|
|
a0057b8a49 | ||
|
|
dbb0269354 | ||
|
|
7fdb617b33 | ||
|
|
d798b0dec9 | ||
|
|
95cf23eb5b | ||
|
|
7e3db20229 | ||
|
|
12689babfb | ||
|
|
211407da44 | ||
|
|
400a84e48d | ||
|
|
7a45640e5e | ||
|
|
8af20885ab | ||
|
|
4033f07272 | ||
|
|
5df4fa297d | ||
|
|
33c5b418d2 | ||
|
|
69d378a17b | ||
|
|
f1b1b8ce53 | ||
|
|
526d4748e0 | ||
|
|
c5f93adbf4 | ||
|
|
61630cbe90 | ||
|
|
8c435e9d6d | ||
|
|
57978b1a6e | ||
|
|
f3c4d9449a | ||
|
|
9b37777f20 | ||
|
|
d6d6c161db | ||
|
|
772bca5ba6 | ||
|
|
036a60a095 | ||
|
|
b3315e1ed4 | ||
|
|
d300e9cf52 | ||
|
|
1f4bcd150f | ||
|
|
9ad8894983 | ||
|
|
9e63ca5eb7 | ||
|
|
ade66242bb | ||
|
|
2c6932b4cb | ||
|
|
5cce9e7205 | ||
|
|
7dd3ad9548 | ||
|
|
f690b76efa | ||
|
|
87b8d6710e | ||
|
|
965b890346 | ||
|
|
02b4e5cc70 | ||
|
|
410add04fb | ||
|
|
b575b1e700 | ||
|
|
5828ee1ada | ||
|
|
a64c80109e | ||
|
|
de47f7f2fa | ||
|
|
19adc7b9e5 | ||
|
|
23f60a59fe | ||
|
|
946ba2e56d | ||
|
|
9dcb7b49fa | ||
|
|
52f5901642 | ||
|
|
d4b4a7e1ff | ||
|
|
4449678b74 | ||
|
|
19e197e0ec | ||
|
|
cfc5202645 | ||
|
|
9b80d9e7aa | ||
|
|
dc409387bd | ||
|
|
1e73a7bda4 | ||
|
|
ef1d62d45c | ||
|
|
c2d82750b1 | ||
|
|
c97d276b36 |
10
.craft.ini
Normal file
10
.craft.ini
Normal file
@@ -0,0 +1,10 @@
|
||||
; 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
|
||||
@@ -2,9 +2,12 @@
|
||||
# SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
include:
|
||||
- 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
|
||||
- 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
|
||||
|
||||
@@ -13,8 +13,10 @@ 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'
|
||||
@@ -25,11 +27,10 @@ Dependencies:
|
||||
'frameworks/qqc2-desktop-style': '@latest-kf6'
|
||||
'frameworks/kio': '@latest-kf6'
|
||||
'frameworks/kwindowsystem': '@latest-kf6'
|
||||
'frameworks/kconfigwidgets': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
- 'on': ['Linux', 'FreeBSD']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
|
||||
- 'on': ['Linux']
|
||||
'require':
|
||||
|
||||
@@ -45,3 +45,7 @@ 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
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "01")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "85")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
@@ -37,6 +37,7 @@ include(ECMAddAppIcon)
|
||||
include(KDEGitCommitHooks)
|
||||
include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
include(ECMAddAndroidApk)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -57,12 +58,12 @@ set_package_properties(Qt6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami2 I18n Notifications Config CoreAddons Sonnet ItemModels)
|
||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
|
||||
set_package_properties(KF6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
set_package_properties(KF6Kirigami2 PROPERTIES
|
||||
set_package_properties(KF6Kirigami PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Kirigami application UI framework"
|
||||
)
|
||||
@@ -76,7 +77,7 @@ if(ANDROID)
|
||||
)
|
||||
else()
|
||||
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle ConfigWidgets KIO WindowSystem StatusNotifierItem)
|
||||
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem)
|
||||
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
|
||||
TYPE RUNTIME
|
||||
)
|
||||
@@ -101,8 +102,7 @@ set_package_properties(QuotientQt6 PROPERTIES
|
||||
PURPOSE "Talk with matrix server"
|
||||
)
|
||||
|
||||
# The android part is just for CI. We do NOT support any builds without E2EE
|
||||
if (NOT TARGET Olm::Olm AND NOT ANDROID)
|
||||
if (NOT TARGET Olm::Olm)
|
||||
message(FATAL_ERROR "NeoChat requires Quotient with the E2EE feature enabled")
|
||||
endif()
|
||||
|
||||
@@ -119,6 +119,7 @@ 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)
|
||||
|
||||
find_package(KQuickImageEditor COMPONENTS)
|
||||
set_package_properties(KQuickImageEditor PROPERTIES
|
||||
@@ -128,7 +129,7 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
||||
PURPOSE "Add image editing capability to image attachments"
|
||||
)
|
||||
|
||||
find_package(QCoro6 0.4 COMPONENTS Core REQUIRED)
|
||||
find_package(QCoro6 0.4 COMPONENTS Core Network REQUIRED)
|
||||
|
||||
qcoro_enable_coroutines()
|
||||
|
||||
@@ -138,6 +139,13 @@ 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)
|
||||
|
||||
@@ -11,6 +11,7 @@ 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
|
||||
|
||||
@@ -95,7 +96,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://matrix.to/#/#neochat:kde.org).
|
||||
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.
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
|
||||
@@ -9,9 +9,9 @@
|
||||
android:versionName="${versionName}"
|
||||
android:versionCode="${versionCode}"
|
||||
android:installLocation="auto">
|
||||
<application android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="NeoChat" android:icon="@drawable/neochat" android:usesCleartextTraffic="true">
|
||||
<application android:name="org.qtproject.qt.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.qt5.android.bindings.QtActivity"
|
||||
android:name="org.qtproject.qt.android.bindings.QtActivity"
|
||||
android:label="NeoChat"
|
||||
android:windowSoftInputMode="adjustResize"
|
||||
android:launchMode="singleTop"
|
||||
|
||||
@@ -12,7 +12,7 @@ buildscript {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.0.2'
|
||||
classpath 'com.android.tools.build:gradle:7.4.1'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ android {
|
||||
* The following variables:
|
||||
* - androidBuildToolsVersion,
|
||||
* - androidCompileSdkVersion
|
||||
* - qt5AndroidDir - holds the path to qt android files
|
||||
* - qtAndroidDir - holds the path to qt android files
|
||||
* needed to build any Qt application
|
||||
* on Android.
|
||||
*
|
||||
@@ -44,17 +44,20 @@ android {
|
||||
* Changing them manually might break the compilation!
|
||||
*******************************************************/
|
||||
|
||||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
compileSdkVersion androidCompileSdkVersion
|
||||
buildToolsVersion androidBuildToolsVersion
|
||||
ndkVersion androidNdkVersion
|
||||
|
||||
// Extract native libraries from the APK
|
||||
packagingOptions.jniLibs.useLegacyPackaging true
|
||||
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
manifest.srcFile 'AndroidManifest.xml'
|
||||
java.srcDirs = [qt5AndroidDir + '/src', 'src', 'java']
|
||||
aidl.srcDirs = [qt5AndroidDir + '/src', 'src', 'aidl']
|
||||
res.srcDirs = [qt5AndroidDir + '/res', 'res']
|
||||
java.srcDirs = [qtAndroidDir + '/src', 'src', 'java']
|
||||
aidl.srcDirs = [qtAndroidDir + '/src', 'src', 'aidl']
|
||||
res.srcDirs = [qtAndroidDir + '/res', 'res']
|
||||
resources.srcDirs = ['src']
|
||||
renderscript.srcDirs = ['src']
|
||||
assets.srcDirs = ['assets']
|
||||
@@ -89,6 +92,7 @@ android {
|
||||
exclude 'lib/*/*_imageformats_qtga_*'
|
||||
exclude 'lib/*/*_imageformats_qtiff_*'
|
||||
exclude 'lib/*/*_qmltooling_*'
|
||||
exclude 'lib/*/*_multimedia_ffmpeg*' // temporary qt6 android fix
|
||||
}
|
||||
|
||||
aaptOptions {
|
||||
|
||||
@@ -21,3 +21,8 @@ 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
|
||||
)
|
||||
|
||||
3
appiumtests/data/sync_response_no_rooms.json
Normal file
3
appiumtests/data/sync_response_no_rooms.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"next_batch": "batch1234"
|
||||
}
|
||||
50
appiumtests/data/sync_response_rooms.json
Normal file
50
appiumtests/data/sync_response_rooms.json
Normal file
@@ -0,0 +1,50 @@
|
||||
{
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,12 @@
|
||||
# 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__)
|
||||
|
||||
|
||||
@app.route("/_matrix/client/v3/login", methods=["GET"])
|
||||
def login_get():
|
||||
result = dict()
|
||||
@@ -12,6 +14,13 @@ 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()
|
||||
@@ -19,15 +28,22 @@ def login_post():
|
||||
abort(403)
|
||||
print(data)
|
||||
result = dict()
|
||||
result["access_token"] = "token_1234"
|
||||
result["access_token"] = "token_login"
|
||||
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 = dict()
|
||||
result["next_batch"] = "batch1234"
|
||||
|
||||
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
|
||||
return result
|
||||
|
||||
@app.route("/.well-known/matrix/client")
|
||||
@@ -37,6 +53,18 @@ 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)
|
||||
|
||||
48
appiumtests/openuserdetailstest.py
Executable file
48
appiumtests/openuserdetailstest.py
Executable file
@@ -0,0 +1,48 @@
|
||||
#!/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()
|
||||
@@ -40,3 +40,21 @@ ecm_add_test(
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME chatbarcachetest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
chatdocumenthandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME chatdocumenthandlertest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
messageeventmodeltest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME messageeventmodeltest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
actionshandlertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
TEST_NAME actionshandlertest
|
||||
)
|
||||
|
||||
43
autotests/actionshandlertest.cpp
Normal file
43
autotests/actionshandlertest.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// 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 <QTest>
|
||||
|
||||
#include "actionshandler.h"
|
||||
#include "chatbarcache.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
class ActionsHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Quotient::Connection *connection = Quotient::Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
ActionsHandler *actionsHandler = new ActionsHandler(this);
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullObject();
|
||||
};
|
||||
|
||||
void ActionsHandlerTest::nullObject()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
actionsHandler->handleMessageEvent(nullptr);
|
||||
|
||||
auto chatBarCache = new ChatBarCache(this);
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
actionsHandler->handleMessageEvent(chatBarCache);
|
||||
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
|
||||
actionsHandler->setRoom(room);
|
||||
QTest::ignoreMessage(QtWarningMsg, "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
actionsHandler->handleMessageEvent(nullptr);
|
||||
|
||||
// The final one should throw no warning so we make sure.
|
||||
QTest::failOnWarning("ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.");
|
||||
actionsHandler->handleMessageEvent(chatBarCache);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(ActionsHandlerTest)
|
||||
#include "actionshandlertest.moc"
|
||||
@@ -12,26 +12,17 @@
|
||||
#include "chatbarcache.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include "testutils.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;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
@@ -47,14 +38,7 @@ private Q_SLOTS:
|
||||
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));
|
||||
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json"));
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::empty()
|
||||
|
||||
36
autotests/chatdocumenthandlertest.cpp
Normal file
36
autotests/chatdocumenthandlertest.cpp
Normal file
@@ -0,0 +1,36 @@
|
||||
// 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"
|
||||
@@ -135,6 +135,21 @@
|
||||
"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
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -373,6 +388,20 @@
|
||||
"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,
|
||||
|
||||
105
autotests/data/test-messageventmodel-sync.json
Normal file
105
autotests/data/test-messageventmodel-sync.json
Normal file
@@ -0,0 +1,105 @@
|
||||
{
|
||||
"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": {
|
||||
"displayname": "Example",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "$exampleMember:example.org",
|
||||
"origin_server_ts": 1432735824653,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@example:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"age": 1234
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"summary": {
|
||||
"m.heroes": [
|
||||
"@example:example.org"
|
||||
],
|
||||
"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": 1,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"type": "m.room.message",
|
||||
"unsigned": {
|
||||
"age": 1232
|
||||
}
|
||||
},
|
||||
{
|
||||
"content": {
|
||||
"displayname": "Example Changed",
|
||||
"membership": "join"
|
||||
},
|
||||
"event_id": "$exampleMemberChnage:example.org",
|
||||
"origin_server_ts": 2,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@example:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"replaces_state": "$exampleMember:example.org",
|
||||
"prev_content": {
|
||||
"displayname": "Example",
|
||||
"membership": "join"
|
||||
},
|
||||
"prev_sender": "@example:example.org",
|
||||
"age": 1234
|
||||
}
|
||||
}
|
||||
],
|
||||
"limited": true,
|
||||
"prev_batch": "t34-23535_0_0"
|
||||
}
|
||||
}
|
||||
22
autotests/data/test-pending-sync.json
Normal file
22
autotests/data/test-pending-sync.json
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"timeline": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"body": "New plain message",
|
||||
"msgtype": "m.text"
|
||||
},
|
||||
"event_id": "$pendingmerge:example.org",
|
||||
"origin_server_ts": 123456,
|
||||
"room_id":"#myroom:kde.org",
|
||||
"sender":"@bob:kde.org",
|
||||
"type":"m.room.message",
|
||||
"unsigned": {
|
||||
"transaction_id": "17017181543521"
|
||||
}
|
||||
}
|
||||
],
|
||||
"limited": true,
|
||||
"prev_batch": "t34-23535_0_0"
|
||||
}
|
||||
}
|
||||
124
autotests/data/test-texthandler-sync.json
Normal file
124
autotests/data/test-texthandler-sync.json
Normal file
@@ -0,0 +1,124 @@
|
||||
{
|
||||
"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"
|
||||
}
|
||||
}
|
||||
@@ -18,70 +18,92 @@
|
||||
#include "neochatroom.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "testutils.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 EventHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestRoom *room = nullptr;
|
||||
TestUtils::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 subtitle();
|
||||
void nullSubtitle();
|
||||
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()
|
||||
{
|
||||
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());
|
||||
room->update(std::move(roomData));
|
||||
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-eventhandler-sync.json"));
|
||||
|
||||
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()
|
||||
@@ -91,6 +113,12 @@ 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");
|
||||
@@ -114,6 +142,12 @@ 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();
|
||||
@@ -131,6 +165,15 @@ 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();
|
||||
@@ -139,6 +182,32 @@ 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();
|
||||
@@ -148,6 +217,16 @@ 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();
|
||||
@@ -169,6 +248,16 @@ 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();
|
||||
@@ -182,6 +271,15 @@ 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();
|
||||
@@ -195,6 +293,15 @@ 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();
|
||||
@@ -206,6 +313,15 @@ 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");
|
||||
@@ -228,6 +344,31 @@ 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::subtitle()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.subtitleText(), QStringLiteral("after: This is an example text message"));
|
||||
|
||||
event = room->messageEvents().at(2).get();
|
||||
eventHandler.setEvent(event);
|
||||
|
||||
QCOMPARE(eventHandler.subtitleText(), QStringLiteral("after: This is a highlight @bob:kde.org and this is a link https://kde.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullSubtitle()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.subtitleText(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::mediaInfo()
|
||||
{
|
||||
auto event = room->messageEvents().at(4).get();
|
||||
@@ -251,6 +392,15 @@ 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();
|
||||
@@ -264,6 +414,15 @@ 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();
|
||||
@@ -272,6 +431,15 @@ 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();
|
||||
@@ -285,6 +453,12 @@ 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();
|
||||
@@ -298,6 +472,12 @@ 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();
|
||||
@@ -311,6 +491,15 @@ 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();
|
||||
@@ -334,6 +523,15 @@ 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();
|
||||
@@ -345,6 +543,15 @@ 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();
|
||||
@@ -369,6 +576,15 @@ 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();
|
||||
@@ -392,6 +608,15 @@ void EventHandlerTest::thread()
|
||||
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();
|
||||
@@ -402,6 +627,18 @@ 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();
|
||||
@@ -431,5 +668,37 @@ 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"
|
||||
|
||||
215
autotests/messageeventmodeltest.cpp
Normal file
215
autotests/messageeventmodeltest.cpp
Normal file
@@ -0,0 +1,215 @@
|
||||
// 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 <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
#include "models/messageeventmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class MessageEventModelTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
MessageEventModel *model = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void init();
|
||||
|
||||
void switchEmptyRoom();
|
||||
void switchSyncedRoom();
|
||||
void simpleTimeline();
|
||||
void syncNewEvents();
|
||||
void pendingEvent();
|
||||
void disconnect();
|
||||
void idToRow();
|
||||
|
||||
void cleanup();
|
||||
};
|
||||
|
||||
void MessageEventModelTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
}
|
||||
|
||||
void MessageEventModelTest::init()
|
||||
{
|
||||
QCOMPARE(model, nullptr);
|
||||
model = new MessageEventModel;
|
||||
}
|
||||
|
||||
// Make sure that basic empty rooms can be switched without crashing.
|
||||
void MessageEventModelTest::switchEmptyRoom()
|
||||
{
|
||||
auto firstRoom = new TestUtils::TestRoom(connection, QStringLiteral("#firstRoom:kde.org"));
|
||||
auto secondRoom = new TestUtils::TestRoom(connection, QStringLiteral("#secondRoom:kde.org"));
|
||||
|
||||
QSignalSpy spy(model, SIGNAL(roomChanged()));
|
||||
|
||||
QCOMPARE(model->room(), nullptr);
|
||||
model->setRoom(firstRoom);
|
||||
QCOMPARE(spy.count(), 1);
|
||||
QCOMPARE(model->room()->id(), QStringLiteral("#firstRoom:kde.org"));
|
||||
model->setRoom(secondRoom);
|
||||
QCOMPARE(spy.count(), 2);
|
||||
QCOMPARE(model->room()->id(), QStringLiteral("#secondRoom:kde.org"));
|
||||
model->setRoom(nullptr);
|
||||
QCOMPARE(spy.count(), 3);
|
||||
QCOMPARE(model->room(), nullptr);
|
||||
}
|
||||
|
||||
// Make sure that rooms with some events can be switched without crashing
|
||||
void MessageEventModelTest::switchSyncedRoom()
|
||||
{
|
||||
auto firstRoom = new TestUtils::TestRoom(connection, QStringLiteral("#firstRoom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
|
||||
auto secondRoom = new TestUtils::TestRoom(connection, QStringLiteral("#secondRoom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
|
||||
|
||||
QSignalSpy spy(model, SIGNAL(roomChanged()));
|
||||
|
||||
QCOMPARE(model->room(), nullptr);
|
||||
model->setRoom(firstRoom);
|
||||
QCOMPARE(spy.count(), 1);
|
||||
QCOMPARE(model->room()->id(), QStringLiteral("#firstRoom:kde.org"));
|
||||
model->setRoom(secondRoom);
|
||||
QCOMPARE(spy.count(), 2);
|
||||
QCOMPARE(model->room()->id(), QStringLiteral("#secondRoom:kde.org"));
|
||||
model->setRoom(nullptr);
|
||||
QCOMPARE(spy.count(), 3);
|
||||
QCOMPARE(model->room(), nullptr);
|
||||
}
|
||||
|
||||
void MessageEventModelTest::simpleTimeline()
|
||||
{
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-messageventmodel-sync.json"));
|
||||
|
||||
model->setRoom(room);
|
||||
QCOMPARE(model->rowCount(), 2);
|
||||
|
||||
QCOMPARE(model->data(model->index(0), MessageEventModel::DelegateTypeRole), DelegateType::State);
|
||||
QCOMPARE(model->data(model->index(0)), QStringLiteral("changed their display name to Example Changed"));
|
||||
|
||||
QCOMPARE(model->data(model->index(1)), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(model->data(model->index(1), MessageEventModel::DelegateTypeRole), DelegateType::Message);
|
||||
QCOMPARE(model->data(model->index(1), MessageEventModel::PlainText), QStringLiteral("This is an example\ntext message"));
|
||||
QCOMPARE(model->data(model->index(1), MessageEventModel::EventIdRole), QStringLiteral("$153456789:example.org"));
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "Index QModelIndex(-1,-1,0x0,QObject(0x0)) is not valid (expected valid)");
|
||||
QCOMPARE(model->data(model->index(-1)), QVariant());
|
||||
QTest::ignoreMessage(QtWarningMsg, "Index QModelIndex(-1,-1,0x0,QObject(0x0)) is not valid (expected valid)");
|
||||
QCOMPARE(model->data(model->index(model->rowCount())), QVariant());
|
||||
}
|
||||
|
||||
// Sync some events into the MessageEventModel's current room and don't crash.
|
||||
void MessageEventModelTest::syncNewEvents()
|
||||
{
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
|
||||
QSignalSpy spy(room, SIGNAL(aboutToAddNewMessages(Quotient::RoomEventsRange)));
|
||||
|
||||
model->setRoom(room);
|
||||
QCOMPARE(model->rowCount(), 0);
|
||||
|
||||
room->syncNewEvents(QLatin1String("test-messageventmodel-sync.json"));
|
||||
|
||||
QCOMPARE(model->rowCount(), 2);
|
||||
QCOMPARE(spy.count(), 1);
|
||||
}
|
||||
|
||||
// Check the adding of pending events to the room doesn't cause any issues in the model.
|
||||
void MessageEventModelTest::pendingEvent()
|
||||
{
|
||||
QSignalSpy spyInsert(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)));
|
||||
QSignalSpy spyRemove(model, SIGNAL(rowsRemoved(const QModelIndex &, int, int)));
|
||||
QSignalSpy spyChanged(model, SIGNAL(dataChanged(const QModelIndex, const QModelIndex, const QList<int> &)));
|
||||
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
|
||||
model->setRoom(room);
|
||||
QCOMPARE(model->rowCount(), 0);
|
||||
|
||||
auto txnId = room->postPlainText("New plain message"_ls);
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
QCOMPARE(spyInsert.count(), 1);
|
||||
|
||||
room->discardMessage(txnId);
|
||||
QCOMPARE(model->rowCount(), 0);
|
||||
QCOMPARE(spyRemove.count(), 1);
|
||||
|
||||
txnId = room->postPlainText("New plain message"_ls);
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
QCOMPARE(spyInsert.count(), 2);
|
||||
|
||||
// We need to manually set the transaction ID of the new message as it will be
|
||||
// different every time.
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + QLatin1String("test-pending-sync.json"));
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
auto root = testSyncJson.object();
|
||||
auto timeline = root["timeline"_ls].toObject();
|
||||
auto events = timeline["events"_ls].toArray();
|
||||
auto firstEvent = events[0].toObject();
|
||||
firstEvent.insert(QLatin1String("unsigned"), QJsonObject{{QLatin1String("transaction_id"), txnId}});
|
||||
events[0] = firstEvent;
|
||||
timeline.insert("events"_ls, events);
|
||||
root.insert("timeline"_ls, timeline);
|
||||
testSyncJson.setObject(root);
|
||||
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, testSyncJson.object());
|
||||
room->update(std::move(roomData));
|
||||
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
// The model will throw multiple data changed signals we need the one that refreshes
|
||||
// the IsPendingRole.
|
||||
QCOMPARE(spyChanged.count() > 0, true);
|
||||
auto isPendingChanged = false;
|
||||
for (auto signal : spyChanged) {
|
||||
auto roles = signal.at(2).toList();
|
||||
if (roles.contains(MessageEventModel::IsPendingRole)) {
|
||||
isPendingChanged = true;
|
||||
}
|
||||
}
|
||||
QCOMPARE(isPendingChanged, true);
|
||||
}
|
||||
|
||||
// Make sure that the signals are disconnecting correctly when a room is switched.
|
||||
void MessageEventModelTest::disconnect()
|
||||
{
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"));
|
||||
model->setRoom(room);
|
||||
|
||||
QSignalSpy spy(model, SIGNAL(rowsInserted(const QModelIndex &, int, int)));
|
||||
|
||||
model->setRoom(nullptr);
|
||||
room->syncNewEvents(QLatin1String("test-messageventmodel-sync.json"));
|
||||
|
||||
QCOMPARE(spy.count(), 0);
|
||||
}
|
||||
|
||||
void MessageEventModelTest::idToRow()
|
||||
{
|
||||
auto room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-min-sync.json"));
|
||||
model->setRoom(room);
|
||||
|
||||
QCOMPARE(model->eventIdToRow(QStringLiteral("$153456789:example.org")), 0);
|
||||
}
|
||||
|
||||
void MessageEventModelTest::cleanup()
|
||||
{
|
||||
delete model;
|
||||
model = nullptr;
|
||||
QCOMPARE(model, nullptr);
|
||||
}
|
||||
|
||||
QTEST_MAIN(MessageEventModelTest)
|
||||
#include "messageeventmodeltest.moc"
|
||||
@@ -5,138 +5,30 @@
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "testutils.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 NeoChatRoomTest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestRoom *room = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void subtitleTextTest();
|
||||
void eventTest();
|
||||
};
|
||||
|
||||
void NeoChatRoomTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
|
||||
|
||||
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 text message"));
|
||||
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), "test-min-sync.json"_ls);
|
||||
}
|
||||
|
||||
void NeoChatRoomTest::eventTest()
|
||||
|
||||
41
autotests/testutils.h
Normal file
41
autotests/testutils.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
|
||||
namespace TestUtils
|
||||
{
|
||||
class TestRoom : public NeoChatRoom
|
||||
{
|
||||
public:
|
||||
TestRoom(Quotient::Connection *connection, const QString &roomName, const QString &syncFileName = {})
|
||||
: NeoChatRoom(connection, roomName, Quotient::JoinState::Join)
|
||||
{
|
||||
syncNewEvents(syncFileName);
|
||||
}
|
||||
|
||||
void update(Quotient::SyncRoomData &&data, bool fromCache = false)
|
||||
{
|
||||
Room::updateData(std::move(data), fromCache);
|
||||
}
|
||||
|
||||
void syncNewEvents(const QString &syncFileName)
|
||||
{
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QLatin1String(DATA_DIR) + u'/' + syncFileName);
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
|
||||
update(std::move(roomData));
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -10,28 +10,21 @@
|
||||
#include <Quotient/syncdata.h>
|
||||
#include <qnamespace.h>
|
||||
|
||||
#include "models/customemojimodel.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include "testutils.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 TextHandlerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestRoom *room = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
@@ -40,6 +33,7 @@ private Q_SLOTS:
|
||||
void stripDisallowedTags();
|
||||
void stripDisallowedAttributes();
|
||||
void emptyCodeTags();
|
||||
void formatBlockQuote();
|
||||
|
||||
void sendSimpleStringCase();
|
||||
void sendSingleParaMarkup();
|
||||
@@ -47,6 +41,9 @@ private Q_SLOTS:
|
||||
void sendBadLinks();
|
||||
void sendEscapeCode();
|
||||
void sendCodeClass();
|
||||
void sendCustomEmoji();
|
||||
void sendCustomEmojiCode_data();
|
||||
void sendCustomEmojiCode();
|
||||
|
||||
void receiveStripReply();
|
||||
void receivePlainTextIn();
|
||||
@@ -66,6 +63,7 @@ private Q_SLOTS:
|
||||
void receiveRichEdited_data();
|
||||
void receiveRichEdited();
|
||||
void receiveLineSeparator();
|
||||
void receiveRichCodeUrl();
|
||||
|
||||
void linkPreviewsMatch_data();
|
||||
void linkPreviewsMatch();
|
||||
@@ -76,134 +74,15 @@ private Q_SLOTS:
|
||||
void TextHandlerTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
|
||||
connection->setAccountData("im.ponies.user_emotes"_ls,
|
||||
QJsonObject{{"images"_ls,
|
||||
QJsonObject{{"test"_ls,
|
||||
QJsonObject{{"body"_ls, "Test custom emoji"_ls},
|
||||
{"url"_ls, "mxc://example.org/test"_ls},
|
||||
{"usage"_ls, QJsonArray{"emoticon"_ls}}}}}}});
|
||||
CustomEmojiModel::instance().setConnection(static_cast<NeoChatConnection *>(connection));
|
||||
|
||||
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));
|
||||
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-texthandler-sync.json"));
|
||||
}
|
||||
|
||||
void TextHandlerTest::allowedAttributes()
|
||||
@@ -265,6 +144,16 @@ 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.");
|
||||
@@ -342,6 +231,39 @@ void TextHandlerTest::sendCodeClass()
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendCustomEmoji()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(":test:");
|
||||
const QString testOutputString = QStringLiteral(
|
||||
"<p><img data-mx-emoticon=\"\" src=\"mxc://example.org/test\" alt=\":test:\" title=\":test:\" height=\"32\" vertical-align=\"middle\" /></p>");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendCustomEmojiCode_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("inline") << QStringLiteral("`:test:`") << QStringLiteral("<p><code>:test:</code></p>");
|
||||
QTest::newRow("block") << QStringLiteral("```\n:test:\n```") << QStringLiteral("<pre><code>:test:\n</code></pre>");
|
||||
}
|
||||
|
||||
// Custom emojis in code blocks should be left alone.
|
||||
void TextHandlerTest::sendCustomEmojiCode()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveStripReply()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(
|
||||
@@ -577,8 +499,9 @@ 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>Edited</blockquote><p> <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>");
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited()
|
||||
@@ -647,5 +570,13 @@ 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"
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
</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>
|
||||
@@ -46,6 +47,7 @@
|
||||
<name xml:lang="uk">NeoChat</name>
|
||||
<name xml:lang="x-test">xxNeoChatxx</name>
|
||||
<name xml:lang="zh-CN">NeoChat</name>
|
||||
<name xml:lang="zh-TW">NeoChat</name>
|
||||
<summary>Chat with your friends on matrix</summary>
|
||||
<summary xml:lang="ar">دردش مع أصدقائك على ماتركس</summary>
|
||||
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
|
||||
@@ -69,6 +71,7 @@
|
||||
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
|
||||
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
|
||||
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
|
||||
<summary xml:lang="zh-TW">在 Matrix 上與您的朋友聊天</summary>
|
||||
<description>
|
||||
<p>NeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami
|
||||
to provide a convergent experience across multiple platforms.</p>
|
||||
@@ -95,10 +98,11 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="tr">NeoChat, anlık iletileşme için merkezi olmayan iletişim protokolü olan Matrix için bir istemcidir. Ailenize, iş arkadaşlarınıza ve arkadaşlarınıza metin iletiler, videolar ve ses dosyaları göndermenize olanak tanır. Birden çok platformda yakınsak bir deneyim sağlamak için KDE Frameworks ve en önemlilerinden Kirigami'yi kullanır.</p>
|
||||
<p xml:lang="uk">NeoChat — клієнт Matrix, децентралізованого протоколу спілкування для миттєвого обміну повідомленнями. За його допомогою ви можете надсилати текстові повідомлення, відео та звукові файли вашій родин, колегами та друзям. У програмі використано бібліотеки KDE, зокрема Kirigami, для надання однорідного середовища на декількох програмних та апаратних платформах.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami to provide a convergent experience across multiple platforms.xx</p>
|
||||
<p xml:lang="zh-TW">NeoChat 是去中心化即時通訊協定 Matrix 的一個用戶端。它讓您可以傳送文字訊息、影片、音訊檔案給您的家人、同事或朋友。NeoChat 使用 KDE frameworks,尤其是 Kirigami,來提供跨平台的響應式體驗。</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 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 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="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>
|
||||
@@ -119,6 +123,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifreleme'nin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
|
||||
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
|
||||
<p xml:lang="x-test">xxNeoChat 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.xx</p>
|
||||
<p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分,這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p>
|
||||
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
|
||||
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
@@ -144,6 +149,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
|
||||
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
|
||||
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
|
||||
<p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故,NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p>
|
||||
<ul>
|
||||
<li>Polls - MSC3381</li>
|
||||
<li xml:lang="ar">التصويت - MSC3381</li>
|
||||
@@ -170,6 +176,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="tr">Anketler - MSC3381</li>
|
||||
<li xml:lang="uk">Опитування - MSC3381</li>
|
||||
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
|
||||
<li xml:lang="zh-TW">投票 - MSC3381</li>
|
||||
<li>Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
|
||||
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
|
||||
@@ -195,6 +202,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="tr">Yapışkan Paketleri - MSC2545</li>
|
||||
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
|
||||
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
|
||||
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
|
||||
<li>Location Events - MSC3488</li>
|
||||
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
|
||||
<li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li>
|
||||
@@ -220,6 +228,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="tr">Konum Etkinlikleri - MSC3488</li>
|
||||
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
|
||||
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
|
||||
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url type="homepage">https://apps.kde.org/neochat/</url>
|
||||
@@ -264,6 +273,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
|
||||
<developer_name xml:lang="x-test">xxThe KDE Communityxx</developer_name>
|
||||
<developer_name xml:lang="zh-CN">KDE 社区</developer_name>
|
||||
<developer_name xml:lang="zh-TW">KDE 社群</developer_name>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<custom>
|
||||
@@ -309,6 +319,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
|
||||
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
|
||||
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
|
||||
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
|
||||
</screenshot>
|
||||
<screenshot environment="windows">
|
||||
<image>https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</image>
|
||||
@@ -336,12 +347,15 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<caption xml:lang="tr">Oturum açma ekranı</caption>
|
||||
<caption xml:lang="uk">Вікно входу</caption>
|
||||
<caption xml:lang="x-test">xxLogin screenxx</caption>
|
||||
<caption xml:lang="zh-TW">登入畫面</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1">
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.08.4" date="2023-12-07"/>
|
||||
<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>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Version=1.5
|
||||
Name=NeoChat
|
||||
Name[ar]=نيوتشات
|
||||
Name[ast]=NeoChat
|
||||
Name[az]=NeoChat
|
||||
Name[ca]=NeoChat
|
||||
Name[ca@valencia]=NeoChat
|
||||
@@ -41,6 +42,7 @@ Name[tr]=NeoChat
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
Name[zh_TW]=NeoChat
|
||||
GenericName=Matrix Client
|
||||
GenericName[ar]=عميل ماتركس
|
||||
GenericName[az]=Matrix Müştərisi
|
||||
@@ -80,6 +82,7 @@ GenericName[tr]=Matrix İstemcisi
|
||||
GenericName[uk]=Клієнт Matrix
|
||||
GenericName[x-test]=xxMatrix Clientxx
|
||||
GenericName[zh_CN]=Matrix 客户端
|
||||
GenericName[zh_TW]=Matrix 用戶端
|
||||
Comment=Client for the Matrix protocol
|
||||
Comment[ar]=عميل لميفاق ماتركس
|
||||
Comment[az]=Matrix protokolu üçün müştəri
|
||||
@@ -118,6 +121,7 @@ Comment[tr]=Matrix protokolü için istemci
|
||||
Comment[uk]=Клієнт протоколу Matrix
|
||||
Comment[x-test]=xxClient for the Matrix protocolxx
|
||||
Comment[zh_CN]=为 Matrix 协议打造的客户端
|
||||
Comment[zh_TW]=Matrix 通訊協定的用戶端
|
||||
MimeType=x-scheme-handler/matrix;
|
||||
Exec=neochat %u
|
||||
Terminal=false
|
||||
|
||||
1428
po/ar/neochat.po
1428
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
4575
po/ast/neochat.po
Normal file
4575
po/ast/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1395
po/az/neochat.po
1395
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1342
po/ca/neochat.po
1342
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1276
po/cs/neochat.po
1276
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1301
po/da/neochat.po
1301
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1431
po/de/neochat.po
1431
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1443
po/el/neochat.po
1443
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1424
po/en_GB/neochat.po
1424
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1327
po/eo/neochat.po
1327
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1367
po/es/neochat.po
1367
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1360
po/eu/neochat.po
1360
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1652
po/fi/neochat.po
1652
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1398
po/fr/neochat.po
1398
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1438
po/hu/neochat.po
1438
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1370
po/ia/neochat.po
1370
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1434
po/id/neochat.po
1434
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1376
po/ie/neochat.po
1376
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1432
po/it/neochat.po
1432
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1252
po/ja/neochat.po
1252
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1361
po/ka/neochat.po
1361
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1410
po/ko/neochat.po
1410
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1253
po/lt/neochat.po
1253
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1376
po/nl/neochat.po
1376
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1367
po/nn/neochat.po
1367
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1374
po/pa/neochat.po
1374
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1386
po/pl/neochat.po
1386
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1425
po/pt/neochat.po
1425
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1398
po/pt_BR/neochat.po
1398
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1448
po/ru/neochat.po
1448
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1394
po/sk/neochat.po
1394
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1377
po/sl/neochat.po
1377
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1430
po/sv/neochat.po
1430
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1555
po/ta/neochat.po
1555
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1290
po/tok/neochat.po
1290
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1406
po/tr/neochat.po
1406
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1374
po/uk/neochat.po
1374
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1355
po/zh_CN/neochat.po
1355
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
2733
po/zh_TW/neochat.po
2733
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -137,6 +137,12 @@ add_library(neochat STATIC
|
||||
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
|
||||
@@ -144,13 +150,14 @@ 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/LoadingPage.qml
|
||||
qml/UserInfoDesktop.qml
|
||||
qml/RoomPage.qml
|
||||
qml/RoomWindow.qml
|
||||
qml/JoinRoomPage.qml
|
||||
@@ -161,17 +168,15 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/ImageEditorPage.qml
|
||||
qml/WelcomePage.qml
|
||||
qml/General.qml
|
||||
qml/Security.qml
|
||||
qml/RoomSecurity.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
|
||||
@@ -285,11 +290,22 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
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"
|
||||
@@ -306,6 +322,13 @@ 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
|
||||
)
|
||||
@@ -326,15 +349,13 @@ 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::ConfigWidgets KF6::WindowSystem ICU::uc)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_COLORSCHEME)
|
||||
target_link_libraries(neochat PUBLIC KF6::WindowSystem ICU::uc)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_ICU)
|
||||
endif()
|
||||
@@ -346,7 +367,28 @@ 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::Kirigami2 KF6::Notifications KF6::ConfigCore KF6::ConfigGui KF6::CoreAddons KF6::SonnetCore KF6::ItemModels QuotientQt6 cmark::cmark QCoro::Core)
|
||||
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
|
||||
)
|
||||
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
@@ -433,7 +475,22 @@ if(ANDROID)
|
||||
"computer-symbolic"
|
||||
"gps"
|
||||
"system-users-symbolic"
|
||||
"map-flat"
|
||||
"documentinfo"
|
||||
"view-list-details"
|
||||
"go-previous"
|
||||
"mail-forward-symbolic"
|
||||
"dialog-warning-symbolic"
|
||||
"object-rotate-left"
|
||||
"object-rotate-right"
|
||||
"add-subtitle"
|
||||
"security-low"
|
||||
"security-low-symbolic"
|
||||
"kde"
|
||||
"list-remove-symbolic"
|
||||
"edit-delete"
|
||||
)
|
||||
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})
|
||||
@@ -452,9 +509,18 @@ 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()
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
@@ -35,11 +34,13 @@ void ActionsHandler::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
|
||||
{
|
||||
if (!chatBarCache) {
|
||||
if (!m_room || !chatBarCache) {
|
||||
qWarning() << "ActionsHandler::handleMessageEvent - called with m_room and/or chatBarCache set to nullptr.";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -60,10 +61,6 @@ void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
|
||||
|
||||
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *mentions)
|
||||
{
|
||||
if (!m_room) {
|
||||
return QString();
|
||||
}
|
||||
|
||||
std::sort(mentions->begin(), mentions->end(), [](const auto &a, const auto &b) -> bool {
|
||||
return a.cursor.anchor() > b.cursor.anchor();
|
||||
});
|
||||
@@ -74,7 +71,7 @@ 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, mention.id));
|
||||
QStringLiteral("[%1](https://matrix.to/#/%2)").arg(mention.text.toHtmlEscaped(), mention.id));
|
||||
}
|
||||
mentions->clear();
|
||||
|
||||
@@ -134,7 +131,6 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
|
||||
}
|
||||
}
|
||||
|
||||
handledText = CustomEmojiModel::instance().preprocessText(handledText);
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(handledText);
|
||||
handledText = textHandler.handleSendText();
|
||||
|
||||
@@ -35,18 +35,20 @@ class ActionsHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
explicit ActionsHandler(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief The room that messages will be sent to.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
explicit ActionsHandler(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void showEffect(const QString &effect);
|
||||
|
||||
public Q_SLOTS:
|
||||
|
||||
@@ -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 NULL;
|
||||
return nullptr;
|
||||
}
|
||||
return pixelArray;
|
||||
}
|
||||
|
||||
@@ -173,3 +173,5 @@ void ChatBarCache::setSavedText(const QString &savedText)
|
||||
{
|
||||
m_savedText = savedText;
|
||||
}
|
||||
|
||||
#include "moc_chatbarcache.cpp"
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <Sonnet/BackgroundChecker>
|
||||
#include <Sonnet/Settings>
|
||||
|
||||
#include "chatdocumenthandler_logging.h"
|
||||
|
||||
class SyntaxHighlighter : public QSyntaxHighlighter
|
||||
{
|
||||
public:
|
||||
@@ -98,7 +100,7 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
|
||||
, m_document(nullptr)
|
||||
, m_cursorPosition(-1)
|
||||
, m_highlighter(new SyntaxHighlighter(this))
|
||||
, m_completionModel(new CompletionModel())
|
||||
, m_completionModel(new CompletionModel(this))
|
||||
{
|
||||
connect(this, &ChatDocumentHandler::roomChanged, this, [this]() {
|
||||
m_completionModel->setRoom(m_room);
|
||||
@@ -130,11 +132,7 @@ int ChatDocumentHandler::completionStartIndex() const
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if !defined(Q_OS_ANDROID)
|
||||
const long long cursor = cursorPosition();
|
||||
#else
|
||||
const auto cursor = cursorPosition();
|
||||
#endif
|
||||
const qsizetype cursor = cursorPosition();
|
||||
const auto &text = getText();
|
||||
|
||||
auto start = std::min(cursor, text.size()) - 1;
|
||||
@@ -148,20 +146,6 @@ 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;
|
||||
@@ -227,6 +211,15 @@ void ChatDocumentHandler::setChatBarCache(ChatBarCache *chatBarCache)
|
||||
|
||||
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();
|
||||
@@ -310,15 +303,17 @@ void ChatDocumentHandler::setSelectionEnd(int position)
|
||||
|
||||
QString ChatDocumentHandler::getText() const
|
||||
{
|
||||
if (!m_room) {
|
||||
return QString();
|
||||
if (!m_chatBarCache) {
|
||||
qCWarning(ChatDocumentHandling) << "getText called with m_chatBarCache set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return m_chatBarCache->text();
|
||||
}
|
||||
|
||||
void ChatDocumentHandler::pushMention(const Mention mention) const
|
||||
{
|
||||
if (!m_room) {
|
||||
if (!m_chatBarCache) {
|
||||
qCWarning(ChatDocumentHandling) << "pushMention called with m_chatBarCache set to nullptr.";
|
||||
return;
|
||||
}
|
||||
m_chatBarCache->mentions()->push_back(mention);
|
||||
|
||||
@@ -62,14 +62,6 @@ 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.
|
||||
*/
|
||||
@@ -96,7 +88,7 @@ class ChatDocumentHandler : public QObject
|
||||
* This is typically provided to a qml component to visualise the current
|
||||
* completion results.
|
||||
*/
|
||||
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
|
||||
Q_PROPERTY(CompletionModel *completionModel READ completionModel CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The current room that the the text document is being handled for.
|
||||
@@ -121,9 +113,6 @@ 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);
|
||||
|
||||
@@ -144,7 +133,6 @@ public:
|
||||
|
||||
Q_INVOKABLE void complete(int index);
|
||||
|
||||
void updateCompletions();
|
||||
CompletionModel *completionModel() const;
|
||||
|
||||
[[nodiscard]] QColor mentionColor() const;
|
||||
@@ -154,12 +142,10 @@ 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();
|
||||
@@ -168,13 +154,10 @@ 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;
|
||||
@@ -188,7 +171,5 @@ private:
|
||||
|
||||
SyntaxHighlighter *m_highlighter = nullptr;
|
||||
|
||||
CompletionModel::AutoCompletionType m_completionType = CompletionModel::None;
|
||||
|
||||
CompletionModel *m_completionModel = nullptr;
|
||||
};
|
||||
|
||||
@@ -6,13 +6,8 @@
|
||||
|
||||
#include <qt6keychain/keychain.h>
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
#include <KLocalizedString>
|
||||
#include <KWindowConfig>
|
||||
#ifdef HAVE_WINDOWSYSTEM
|
||||
#include <KWindowEffects>
|
||||
#endif
|
||||
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
@@ -47,6 +42,8 @@
|
||||
#include "trayicon_sni.h"
|
||||
#endif
|
||||
|
||||
bool testMode = false;
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
Controller::Controller(QObject *parent)
|
||||
@@ -61,11 +58,22 @@ Controller::Controller(QObject *parent)
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||
#endif
|
||||
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
invokeLogin();
|
||||
});
|
||||
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();
|
||||
});
|
||||
}
|
||||
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [] {
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
delete m_trayIcon;
|
||||
NeoChatConfig::self()->save();
|
||||
});
|
||||
|
||||
@@ -97,16 +105,30 @@ 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, [connection]() {
|
||||
connect(connection, &NeoChatConnection::syncDone, this, [this, connection]() {
|
||||
NotificationsManager::instance().handleNotifications(connection);
|
||||
});
|
||||
connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
|
||||
connection->setupPushNotifications(m_endpoint);
|
||||
});
|
||||
}
|
||||
oldAccountCount = m_accountRegistry.size();
|
||||
});
|
||||
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
m_pushRuleModel = new PushRuleModel;
|
||||
#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 "ientConnection : m_accountRegistry) {
|
||||
auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
|
||||
connection->setupPushNotifications(endpoint);
|
||||
}
|
||||
});
|
||||
|
||||
connector->registerClient(i18n("Receiving push notifications"));
|
||||
|
||||
m_endpoint = connector->endpoint();
|
||||
#endif
|
||||
}
|
||||
|
||||
Controller &Controller::instance()
|
||||
@@ -175,6 +197,8 @@ 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;
|
||||
@@ -194,6 +218,8 @@ 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);
|
||||
@@ -201,21 +227,21 @@ void Controller::invokeLogin()
|
||||
});
|
||||
connect(connection, &NeoChatConnection::loginError, this, [this, connection](const QString &error, const QString &) {
|
||||
if (error == "Unrecognised access token"_ls) {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"), {});
|
||||
connection->logout(false);
|
||||
} else if (error == "Connection closed"_ls) {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error), {});
|
||||
// Failed due to network connection issue. This might happen when the homeserver is
|
||||
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
|
||||
// connect to the homeserver. In this case, we don't want to do logout().
|
||||
} else {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error), {});
|
||||
connection->logout(true);
|
||||
}
|
||||
Q_EMIT initiated();
|
||||
});
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error), {});
|
||||
});
|
||||
connection->assumeIdentity(account.userId(), accessToken);
|
||||
});
|
||||
@@ -240,17 +266,17 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const Accoun
|
||||
|
||||
switch (job->error()) {
|
||||
case QKeychain::EntryNotFound:
|
||||
Q_EMIT globalErrorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
|
||||
Q_EMIT errorOccured(i18n("Access token wasn't found"), i18n("Maybe it was deleted?"));
|
||||
break;
|
||||
case QKeychain::AccessDeniedByUser:
|
||||
case QKeychain::AccessDenied:
|
||||
Q_EMIT globalErrorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
|
||||
Q_EMIT errorOccured(i18n("Access to keychain was denied."), i18n("Please allow NeoChat to read the access token"));
|
||||
break;
|
||||
case QKeychain::NoBackendAvailable:
|
||||
Q_EMIT globalErrorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
|
||||
Q_EMIT errorOccured(i18n("No keychain available."), i18n("Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
|
||||
break;
|
||||
case QKeychain::OtherError:
|
||||
Q_EMIT globalErrorOccured(i18n("Unable to read access token"), job->errorString());
|
||||
Q_EMIT errorOccured(i18n("Unable to read access token"), job->errorString());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -304,9 +330,6 @@ void Controller::setQuitOnLastWindowClosed()
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
}
|
||||
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -330,20 +353,6 @@ 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."));
|
||||
@@ -354,12 +363,6 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
|
||||
}
|
||||
NeoChatConfig::self()->save();
|
||||
Q_EMIT activeConnectionChanged();
|
||||
Q_EMIT activeConnectionIndexChanged();
|
||||
}
|
||||
|
||||
PushRuleModel *Controller::pushRuleModel() const
|
||||
{
|
||||
return m_pushRuleModel;
|
||||
}
|
||||
|
||||
void Controller::saveWindowGeometry()
|
||||
@@ -367,79 +370,25 @@ 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();
|
||||
@@ -470,14 +419,6 @@ 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
|
||||
@@ -493,3 +434,8 @@ AccountRegistry &Controller::accounts()
|
||||
}
|
||||
|
||||
#include "moc_controller.cpp"
|
||||
|
||||
void Controller::setTestMode(bool test)
|
||||
{
|
||||
testMode = test;
|
||||
}
|
||||
|
||||
100
src/controller.h
100
src/controller.h
@@ -3,18 +3,19 @@
|
||||
|
||||
#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;
|
||||
@@ -49,31 +50,11 @@ 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.
|
||||
*
|
||||
@@ -81,17 +62,9 @@ class Controller : public QObject
|
||||
*/
|
||||
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the status after an attempt to change the password on an account.
|
||||
*/
|
||||
enum PasswordStatus {
|
||||
Success, /**< The password was successfully changed. */
|
||||
Wrong, /**< The current password entered was wrong. */
|
||||
Other, /**< An unknown problem occurred. */
|
||||
};
|
||||
Q_ENUM(PasswordStatus)
|
||||
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
|
||||
|
||||
public:
|
||||
static Controller &instance();
|
||||
static Controller *create(QQmlEngine *engine, QJSEngine *)
|
||||
{
|
||||
@@ -102,8 +75,6 @@ public:
|
||||
void setActiveConnection(NeoChatConnection *connection);
|
||||
[[nodiscard]] NeoChatConnection *activeConnection() const;
|
||||
|
||||
[[nodiscard]] PushRuleModel *pushRuleModel() const;
|
||||
|
||||
/**
|
||||
* @brief Add a new connection to the account registry.
|
||||
*/
|
||||
@@ -114,34 +85,13 @@ 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.
|
||||
*
|
||||
@@ -151,20 +101,6 @@ 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.
|
||||
*
|
||||
@@ -172,8 +108,16 @@ 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);
|
||||
|
||||
@@ -184,13 +128,11 @@ 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();
|
||||
@@ -198,11 +140,7 @@ private Q_SLOTS:
|
||||
void setQuitOnLastWindowClosed();
|
||||
|
||||
Q_SIGNALS:
|
||||
/// Error occurred because of user inputs
|
||||
void errorOccured(const QString &error);
|
||||
|
||||
/// Error occurred because of server or bug in NeoChat
|
||||
void globalErrorOccured(QString error, QString detail);
|
||||
void errorOccured(const QString &error, const QString &detail);
|
||||
void syncDone();
|
||||
void connectionAdded(NeoChatConnection *connection);
|
||||
void connectionDropped(NeoChatConnection *connection);
|
||||
@@ -210,10 +148,8 @@ Q_SIGNALS:
|
||||
void quitOnLastWindowClosedChanged();
|
||||
void unreadCountChanged();
|
||||
void activeConnectionChanged();
|
||||
void passwordStatus(Controller::PasswordStatus status);
|
||||
void userConsentRequired(QUrl url);
|
||||
void isOnlineChanged(bool isOnline);
|
||||
void activeConnectionIndexChanged();
|
||||
void accountsLoadingChanged();
|
||||
|
||||
public Q_SLOTS:
|
||||
void saveWindowGeometry();
|
||||
|
||||
@@ -40,6 +40,8 @@ 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);
|
||||
|
||||
@@ -19,6 +19,7 @@
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
|
||||
#include "delegatetype.h"
|
||||
#include "eventhandler_logging.h"
|
||||
#include "events/pollevent.h"
|
||||
#include "linkpreviewer.h"
|
||||
@@ -50,6 +51,10 @@ 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;
|
||||
}
|
||||
@@ -164,6 +169,28 @@ 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->safeMemberName(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) {
|
||||
@@ -181,7 +208,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) << "getTime called with m_event set to nullptr.";
|
||||
qCWarning(EventHandling) << "getTimeString called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (isPending && lastUpdated == QDateTime()) {
|
||||
@@ -216,6 +243,15 @@ 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;
|
||||
}
|
||||
@@ -609,8 +645,25 @@ QString EventHandler::getGenericBody() const
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
QString EventHandler::subtitleText() const
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "subtitleText called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return singleLineAuthorDisplayname() + (m_event->isStateEvent() ? QLatin1String(" ") : QLatin1String(": ")) + getPlainBody(true);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -641,12 +694,12 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
// Get the mxc URL for the media.
|
||||
if (!fileInfo->url().isValid() || eventId.isEmpty()) {
|
||||
if (!fileInfo->url().isValid() || fileInfo->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
QUrl source = m_room->makeMediaUrl(eventId, fileInfo->url());
|
||||
|
||||
if (source.isValid() && source.scheme() == QStringLiteral("mxc")) {
|
||||
if (source.isValid()) {
|
||||
mediaInfo["source"_ls] = source;
|
||||
} else {
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
@@ -723,6 +776,14 @@ 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;
|
||||
}
|
||||
@@ -754,7 +815,7 @@ QSharedPointer<ReactionModel> EventHandler::getReactions() const
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getReactions called with m_room set to nullptr.";
|
||||
return {};
|
||||
return nullptr;
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReactions called with m_event set to nullptr.";
|
||||
@@ -806,16 +867,33 @@ 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;
|
||||
@@ -903,6 +981,11 @@ QVariantMap EventHandler::getReplyMediaInfo() const
|
||||
|
||||
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));
|
||||
@@ -910,6 +993,11 @@ bool EventHandler::isThreaded() const
|
||||
|
||||
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) {
|
||||
@@ -925,6 +1013,11 @@ QString EventHandler::threadRoot() const
|
||||
|
||||
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.
|
||||
@@ -935,6 +1028,11 @@ 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.
|
||||
@@ -945,6 +1043,11 @@ 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 {};
|
||||
@@ -954,6 +1057,15 @@ 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;
|
||||
@@ -961,6 +1073,15 @@ 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());
|
||||
|
||||
@@ -981,6 +1102,15 @@ 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());
|
||||
|
||||
@@ -993,6 +1123,15 @@ 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());
|
||||
|
||||
|
||||
@@ -111,6 +111,17 @@ 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.
|
||||
*/
|
||||
@@ -195,6 +206,14 @@ public:
|
||||
*/
|
||||
QString getGenericBody() const;
|
||||
|
||||
/**
|
||||
* @brief Output a string for the event to be used as a RoomList subtitle.
|
||||
*
|
||||
* The output includes the username followed by the plain message, all with no
|
||||
* line breaks.
|
||||
*/
|
||||
QString subtitleText() const;
|
||||
|
||||
/**
|
||||
* @brief Return the media info for the event.
|
||||
*
|
||||
|
||||
@@ -117,7 +117,7 @@ public:
|
||||
file.write(buf.constData(), buf.size());
|
||||
file.flush();
|
||||
|
||||
if (oldHandler && (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled)) {
|
||||
if (oldHandler && (!context.category || (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled))) {
|
||||
oldHandler(type, context, message);
|
||||
}
|
||||
}
|
||||
@@ -197,7 +197,6 @@ void messageHandler(QtMsgType type, const QMessageLogContext &context, const QSt
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
sInstance()->log(QtInfoMsg, context, message);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,7 +82,7 @@ void LoginHelper::init()
|
||||
m_connection = nullptr;
|
||||
});
|
||||
connect(m_connection, &Connection::networkError, this, [this](QString error, const QString &, int, int) {
|
||||
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
|
||||
m_isLoggingIn = false;
|
||||
Q_EMIT isLoggingInChanged();
|
||||
});
|
||||
@@ -97,10 +97,11 @@ void LoginHelper::init()
|
||||
});
|
||||
|
||||
connect(m_connection, &Connection::resolveError, this, [](QString error) {
|
||||
Q_EMIT Controller::instance().globalErrorOccured(i18n("Network Error"), std::move(error));
|
||||
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
|
||||
});
|
||||
|
||||
connectSingleShot(m_connection, &Connection::syncDone, this, []() {
|
||||
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
|
||||
Q_EMIT loaded();
|
||||
Q_EMIT Controller::instance().initiated();
|
||||
});
|
||||
}
|
||||
|
||||
14
src/login.h
14
src/login.h
@@ -79,7 +79,17 @@ class LoginHelper : public QObject
|
||||
Q_PROPERTY(bool isInvalidPassword READ isInvalidPassword NOTIFY isInvalidPasswordChanged)
|
||||
|
||||
public:
|
||||
explicit LoginHelper(QObject *parent = nullptr);
|
||||
static LoginHelper &instance()
|
||||
{
|
||||
static LoginHelper _instance;
|
||||
return _instance;
|
||||
}
|
||||
|
||||
static LoginHelper *create(QQmlEngine *engine, QJSEngine *)
|
||||
{
|
||||
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||
return &instance();
|
||||
}
|
||||
|
||||
Q_INVOKABLE void init();
|
||||
|
||||
@@ -125,6 +135,7 @@ Q_SIGNALS:
|
||||
void isLoggingInChanged();
|
||||
void isLoggedInChanged();
|
||||
void isInvalidPasswordChanged();
|
||||
void loaded();
|
||||
|
||||
private:
|
||||
void setHomeserverReachable(bool reachable);
|
||||
@@ -141,4 +152,5 @@ private:
|
||||
bool m_isLoggingIn = false;
|
||||
bool m_isLoggedIn = false;
|
||||
bool m_invalidPassword = false;
|
||||
explicit LoginHelper(QObject *parent = nullptr);
|
||||
};
|
||||
|
||||
70
src/main.cpp
70
src/main.cpp
@@ -35,9 +35,9 @@
|
||||
#include "neochat-version.h"
|
||||
|
||||
#include <Quotient/networkaccessmanager.h>
|
||||
#include <Quotient/util.h>
|
||||
|
||||
#include "blurhashimageprovider.h"
|
||||
#include "colorschemer.h"
|
||||
#include "controller.h"
|
||||
#include "logger.h"
|
||||
#include "matriximageprovider.h"
|
||||
@@ -45,10 +45,6 @@
|
||||
#include "roommanager.h"
|
||||
#include "windowcontroller.h"
|
||||
|
||||
#ifdef HAVE_COLORSCHEME
|
||||
#include "colorschemer.h"
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
#include "runner.h"
|
||||
#include <QDBusConnection>
|
||||
@@ -125,7 +121,7 @@ int main(int argc, char *argv[])
|
||||
font.setHintingPreference(QFont::PreferNoHinting);
|
||||
app.setFont(font);
|
||||
#endif
|
||||
KLocalizedString::setApplicationDomain("neochat");
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
|
||||
QGuiApplication::setOrganizationName("KDE"_ls);
|
||||
|
||||
@@ -135,7 +131,11 @@ 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"));
|
||||
about.addAuthor(i18n("Carl Schwan"),
|
||||
i18n("Maintainer"),
|
||||
QStringLiteral("carl@carlschwan.eu"),
|
||||
QStringLiteral("https://carlschwan.eu"),
|
||||
QStringLiteral("https://carlschwan.eu/avatar.png"));
|
||||
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 +144,7 @@ int main(int argc, char *argv[])
|
||||
about.setOrganizationDomain("kde.org");
|
||||
|
||||
about.addComponent(QStringLiteral("libQuotient"),
|
||||
i18n("A Qt5 library to write cross-platform clients for Matrix"),
|
||||
i18n("A Qt library to write cross-platform clients for Matrix"),
|
||||
i18nc("<version number> (built against <possibly different version number>)",
|
||||
"%1 (built against %2)",
|
||||
Quotient::versionString(),
|
||||
@@ -157,9 +157,7 @@ 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
|
||||
@@ -168,11 +166,43 @@ 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();
|
||||
@@ -184,7 +214,6 @@ int main(int argc, char *argv[])
|
||||
QQmlApplicationEngine engine;
|
||||
|
||||
#ifdef HAVE_KDBUSADDONS
|
||||
KDBusService service(KDBusService::Unique);
|
||||
service.connect(&service,
|
||||
&KDBusService::activateRequested,
|
||||
&RoomManager::instance(),
|
||||
@@ -203,7 +232,7 @@ int main(int argc, char *argv[])
|
||||
auto args = arguments;
|
||||
args.removeFirst();
|
||||
for (const auto &arg : args) {
|
||||
RoomManager::instance().openResource(arg);
|
||||
RoomManager::instance().resolveResource(arg);
|
||||
}
|
||||
});
|
||||
#endif
|
||||
@@ -212,22 +241,13 @@ 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"), new MatrixImageProvider);
|
||||
engine.addImageProvider(QLatin1String("mxc"), MatrixImageProvider::create(&engine, &engine));
|
||||
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
|
||||
|
||||
engine.load(QUrl(QStringLiteral("qrc:/org/kde/neochat/qml/main.qml")));
|
||||
@@ -240,8 +260,8 @@ int main(int argc, char *argv[])
|
||||
}
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
Runner runner;
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, &runner, QDBusConnection::ExportScriptableContents);
|
||||
auto runner = Runner::create(&engine, &engine);
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, runner, QDBusConnection::ExportScriptableContents);
|
||||
#endif
|
||||
|
||||
QWindow *window = windowFromEngine(&engine);
|
||||
|
||||
@@ -13,11 +13,13 @@
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatconnection.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
|
||||
ThumbnailResponse::ThumbnailResponse(QString id, QSize size, NeoChatConnection *connection)
|
||||
: mediaId(std::move(id))
|
||||
, requestedSize(size)
|
||||
, localFile(QStringLiteral("%1/image_provider/%2-%3x%4.png")
|
||||
@@ -25,6 +27,7 @@ ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
|
||||
mediaId,
|
||||
QString::number(requestedSize.width()),
|
||||
QString::number(requestedSize.height())))
|
||||
, m_connection(connection)
|
||||
, errorStr("Image request hasn't started"_ls)
|
||||
{
|
||||
if (requestedSize.isEmpty()) {
|
||||
@@ -51,24 +54,24 @@ ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
if (!m_connection) {
|
||||
qWarning() << "Current connection is null";
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute a request on the main thread asynchronously
|
||||
moveToThread(Controller::instance().activeConnection()->thread());
|
||||
moveToThread(m_connection->thread());
|
||||
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ThumbnailResponse::startRequest()
|
||||
{
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
// Runs in the main thread, not QML thread
|
||||
Q_ASSERT(QThread::currentThread() == Controller::instance().activeConnection()->thread());
|
||||
job = Controller::instance().activeConnection()->getThumbnail(mediaId, requestedSize);
|
||||
Q_ASSERT(QThread::currentThread() == m_connection->thread());
|
||||
job = m_connection->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);
|
||||
@@ -118,7 +121,7 @@ QString ThumbnailResponse::errorString() const
|
||||
|
||||
QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||
{
|
||||
return new ThumbnailResponse(id, requestedSize);
|
||||
return new ThumbnailResponse(id, requestedSize, m_connection);
|
||||
}
|
||||
|
||||
#include "moc_matriximageprovider.cpp"
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
|
||||
#include <QReadWriteLock>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
/**
|
||||
* @class ThumbnailResponse
|
||||
*
|
||||
@@ -21,7 +23,7 @@ class ThumbnailResponse : public QQuickImageResponse
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ThumbnailResponse(QString mediaId, QSize requestedSize);
|
||||
explicit ThumbnailResponse(QString mediaId, QSize requestedSize, NeoChatConnection *m_connection);
|
||||
~ThumbnailResponse() override = default;
|
||||
|
||||
private Q_SLOTS:
|
||||
@@ -33,6 +35,7 @@ private:
|
||||
QSize requestedSize;
|
||||
const QString localFile;
|
||||
Quotient::MediaThumbnailJob *job = nullptr;
|
||||
NeoChatConnection *m_connection;
|
||||
|
||||
QImage image;
|
||||
QString errorStr;
|
||||
@@ -51,11 +54,27 @@ 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;
|
||||
};
|
||||
|
||||
@@ -24,6 +24,10 @@ int AccountEmoticonModel::rowCount(const QModelIndex &index) const
|
||||
|
||||
QVariant AccountEmoticonModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto &row = index.row();
|
||||
const auto &image = m_images->images[row];
|
||||
if (role == UrlRole) {
|
||||
@@ -92,6 +96,10 @@ void AccountEmoticonModel::setConnection(Connection *connection)
|
||||
|
||||
void AccountEmoticonModel::reloadEmoticons()
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject json;
|
||||
if (m_connection->hasAccountData("im.ponies.user_emotes"_ls)) {
|
||||
json = m_connection->accountData("im.ponies.user_emotes"_ls)->contentJson();
|
||||
@@ -104,6 +112,10 @@ void AccountEmoticonModel::reloadEmoticons()
|
||||
|
||||
void AccountEmoticonModel::deleteEmoticon(int index)
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject data;
|
||||
m_images->images.removeAt(index);
|
||||
m_images->fillJson(&data);
|
||||
@@ -112,6 +124,10 @@ void AccountEmoticonModel::deleteEmoticon(int index)
|
||||
|
||||
void AccountEmoticonModel::setEmoticonBody(int index, const QString &text)
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_images->images[index].body = text;
|
||||
QJsonObject data;
|
||||
m_images->fillJson(&data);
|
||||
@@ -120,6 +136,10 @@ void AccountEmoticonModel::setEmoticonBody(int index, const QString &text)
|
||||
|
||||
void AccountEmoticonModel::setEmoticonShortcode(int index, const QString &shortcode)
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_images->images[index].shortcode = shortcode;
|
||||
QJsonObject data;
|
||||
m_images->fillJson(&data);
|
||||
@@ -128,6 +148,9 @@ void AccountEmoticonModel::setEmoticonShortcode(int index, const QString &shortc
|
||||
|
||||
void AccountEmoticonModel::setEmoticonImage(int index, const QUrl &source)
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
doSetEmoticonImage(index, source);
|
||||
}
|
||||
|
||||
@@ -166,6 +189,9 @@ QCoro::Task<void> AccountEmoticonModel::doAddEmoticon(QUrl source, QString short
|
||||
|
||||
void AccountEmoticonModel::addEmoticon(const QUrl &source, const QString &shortcode, const QString &description, const QString &type)
|
||||
{
|
||||
if (m_connection == nullptr) {
|
||||
return;
|
||||
}
|
||||
doAddEmoticon(source, shortcode, description, type);
|
||||
}
|
||||
|
||||
|
||||
@@ -235,7 +235,7 @@ QList<ActionsModel::Action> actions{
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||
Controller::instance().joinRoom(text);
|
||||
RoomManager::instance().resolveResource(text, "join"_ls);
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -261,7 +261,7 @@ QList<ActionsModel::Action> actions{
|
||||
return QString();
|
||||
}
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
auto connection = room->connection();
|
||||
const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
|
||||
if (parts.length() >= 2) {
|
||||
RoomManager::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
|
||||
@@ -285,12 +285,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 (Controller::instance().activeConnection()->room(text) || Controller::instance().activeConnection()->roomByAlias(text)) {
|
||||
if (room->connection()->room(text) || room->connection()->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));
|
||||
Controller::instance().joinRoom(text);
|
||||
RoomManager::instance().resolveResource(text, "join"_ls);
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
|
||||
@@ -15,13 +15,28 @@
|
||||
|
||||
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 (!Controller::instance().activeConnection()) {
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -53,10 +68,10 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
|
||||
auto job = m_connection->uploadFile(location.toLocalFile());
|
||||
|
||||
connect(job, &BaseJob::success, this, [name, location, job] {
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
connect(job, &BaseJob::success, this, [name, location, job, this] {
|
||||
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
|
||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||
auto emojiData = json["images"_ls].toObject();
|
||||
|
||||
@@ -78,7 +93,7 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
});
|
||||
|
||||
json["images"_ls] = emojiData;
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
m_connection->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,7 +101,7 @@ void CustomEmojiModel::removeEmoji(const QString &name)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
const auto &data = m_connection->accountData("im.ponies.user_emotes"_ls);
|
||||
Q_ASSERT(data);
|
||||
auto json = data->contentJson();
|
||||
const QString _name = name.mid(1).chopped(1);
|
||||
@@ -109,24 +124,25 @@ void CustomEmojiModel::removeEmoji(const QString &name)
|
||||
emojiData.remove(_name);
|
||||
json["emoticons"_ls] = emojiData;
|
||||
}
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
m_connection->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
}
|
||||
|
||||
CustomEmojiModel::CustomEmojiModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, [this]() {
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
connect(this, &CustomEmojiModel::connectionChanged, this, [this]() {
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
CustomEmojiModel::fetchEmojis();
|
||||
connect(Controller::instance().activeConnection(), &Connection::accountDataChanged, this, [this](const QString &id) {
|
||||
connect(m_connection, &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
|
||||
@@ -172,22 +188,14 @@ QHash<int, QByteArray> CustomEmojiModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
QString CustomEmojiModel::preprocessText(const QString &text)
|
||||
QString CustomEmojiModel::preprocessText(QString text)
|
||||
{
|
||||
auto parts = text.split("```"_ls);
|
||||
bool skip = true;
|
||||
for (auto &part : parts) {
|
||||
skip = !skip;
|
||||
if (skip) {
|
||||
continue;
|
||||
}
|
||||
for (const auto &emoji : std::as_const(m_emojis)) {
|
||||
part.replace(
|
||||
emoji.regexp,
|
||||
QStringLiteral(R"(<img data-mx-emoticon="" src="%1" alt="%2" title="%2" height="32" vertical-align="middle" />)").arg(emoji.url, emoji.name));
|
||||
}
|
||||
for (const auto &emoji : std::as_const(m_emojis)) {
|
||||
text.replace(
|
||||
emoji.regexp,
|
||||
QStringLiteral(R"(<img data-mx-emoticon="" src="%1" alt="%2" title="%2" height="32" vertical-align="middle" />)").arg(emoji.url, emoji.name));
|
||||
}
|
||||
return parts.join("```"_ls);
|
||||
return text;
|
||||
}
|
||||
|
||||
QVariantList CustomEmojiModel::filterModel(const QString &filter)
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
#include <QRegularExpression>
|
||||
#include <memory>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
struct CustomEmoji {
|
||||
QString name; // with :semicolons:
|
||||
QString url; // mxc://
|
||||
@@ -31,6 +33,8 @@ class CustomEmojiModel : public QAbstractListModel
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
@@ -81,7 +85,7 @@ public:
|
||||
/**
|
||||
* @brief Substitute any custom emojis for an image in the input text.
|
||||
*/
|
||||
Q_INVOKABLE QString preprocessText(const QString &it);
|
||||
Q_INVOKABLE QString preprocessText(QString text);
|
||||
|
||||
/**
|
||||
* @brief Return a list of custom emojis where the name contains the filter text.
|
||||
@@ -98,9 +102,16 @@ 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();
|
||||
};
|
||||
|
||||
@@ -24,8 +24,8 @@ DevicesModel::DevicesModel(QObject *parent)
|
||||
|
||||
void DevicesModel::fetchDevices()
|
||||
{
|
||||
if (Controller::instance().activeConnection()) {
|
||||
auto job = Controller::instance().activeConnection()->callApi<GetDevicesJob>();
|
||||
if (m_connection) {
|
||||
auto job = m_connection->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 = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId);
|
||||
auto job = m_connection->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, Controller::instance().activeConnection()->user()->id()}};
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||
auto *innerJob = m_connection->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 = Controller::instance().activeConnection()->callApi<UpdateDeviceJob>(m_devices[index].deviceId, name);
|
||||
auto job = m_connection->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 == Controller::instance().activeConnection()->userId()) {
|
||||
if (userId == m_connection->userId()) {
|
||||
fetchDevices();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -102,6 +102,9 @@ 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();
|
||||
|
||||
@@ -1,112 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "keywordnotificationrulemodel.h"
|
||||
#include "controller.h"
|
||||
#include "notificationsmanager.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/converters.h>
|
||||
#include <Quotient/csapi/definitions/push_ruleset.h>
|
||||
#include <Quotient/csapi/pushrules.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
if (Controller::instance().activeConnection()) {
|
||||
controllerConnectionChanged();
|
||||
}
|
||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged);
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::controllerConnectionChanged()
|
||||
{
|
||||
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &KeywordNotificationRuleModel::updateNotificationRules);
|
||||
updateNotificationRules("m.push_rules");
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::updateNotificationRules(const QString &type)
|
||||
{
|
||||
if (type != "m.push_rules") {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
|
||||
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"].toObject());
|
||||
const QList<Quotient::PushRule> contentRules = ruleData.content;
|
||||
|
||||
beginResetModel();
|
||||
m_notificationRules.clear();
|
||||
for (const auto &i : contentRules) {
|
||||
if (!m_notificationRules.contains(i.ruleId) && i.ruleId[0] != '.') {
|
||||
m_notificationRules.append(i.ruleId);
|
||||
}
|
||||
}
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant KeywordNotificationRuleModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (index.row() >= m_notificationRules.count()) {
|
||||
qDebug() << "KeywordNotificationRuleModel, something's wrong: index.row() >= m_notificationRules.count()";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == NameRole) {
|
||||
return m_notificationRules.at(index.row());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
int KeywordNotificationRuleModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
|
||||
return m_notificationRules.count();
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::addKeyword(const QString &keyword)
|
||||
{
|
||||
if (m_notificationRules.count() == 0) {
|
||||
NotificationsManager::instance().initializeKeywordNotificationAction();
|
||||
}
|
||||
|
||||
const QList<QVariant> actions = NotificationsManager::instance().getKeywordNotificationActions();
|
||||
|
||||
auto job = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<Quotient::SetPushRuleJob>("global", "content", keyword, actions, "", "", QList<Quotient::PushCondition>(), keyword);
|
||||
connect(job, &Quotient::BaseJob::success, this, [this, keyword]() {
|
||||
beginInsertRows(QModelIndex(), m_notificationRules.count(), m_notificationRules.count());
|
||||
m_notificationRules.append(keyword);
|
||||
endInsertRows();
|
||||
});
|
||||
}
|
||||
|
||||
void KeywordNotificationRuleModel::removeKeywordAtIndex(int index)
|
||||
{
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>("global", "content", m_notificationRules[index]);
|
||||
connect(job, &Quotient::BaseJob::success, this, [this, index]() {
|
||||
beginRemoveRows(QModelIndex(), index, index);
|
||||
m_notificationRules.removeAt(index);
|
||||
endRemoveRows();
|
||||
|
||||
if (m_notificationRules.count() == 0) {
|
||||
NotificationsManager::instance().deactivateKeywordNotificationAction();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> KeywordNotificationRuleModel::roleNames() const
|
||||
{
|
||||
return {{NameRole, QByteArrayLiteral("name")}};
|
||||
}
|
||||
|
||||
#include "moc_keywordnotificationrulemodel.cpp"
|
||||
@@ -1,66 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/csapi/definitions/push_rule.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
/**
|
||||
* @class KeywordNotificationRuleModel
|
||||
*
|
||||
* This class defines the model for managing notification push rule keywords.
|
||||
*/
|
||||
class KeywordNotificationRuleModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
NameRole = Qt::DisplayRole, /**< The push rule keyword. */
|
||||
};
|
||||
|
||||
KeywordNotificationRuleModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Add a new keyword to the model.
|
||||
*/
|
||||
Q_INVOKABLE void addKeyword(const QString &keyword);
|
||||
|
||||
/**
|
||||
* @brief Remove a keyword from the model.
|
||||
*/
|
||||
Q_INVOKABLE void removeKeywordAtIndex(int index);
|
||||
|
||||
private Q_SLOTS:
|
||||
void controllerConnectionChanged();
|
||||
void updateNotificationRules(const QString &type);
|
||||
|
||||
private:
|
||||
QList<QString> m_notificationRules;
|
||||
};
|
||||
@@ -45,10 +45,6 @@ LocationsModel::LocationsModel(QObject *parent)
|
||||
});
|
||||
|
||||
connect(this, &LocationsModel::rowsInserted, this, &LocationsModel::boundingBoxChanged);
|
||||
|
||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||
});
|
||||
}
|
||||
|
||||
void LocationsModel::addLocation(const RoomMessageEvent *event)
|
||||
@@ -135,4 +131,12 @@ QRectF LocationsModel::boundingBox() const
|
||||
return bbox;
|
||||
}
|
||||
|
||||
bool LocationsModel::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::ApplicationPaletteChange) {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||
}
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
#include "moc_locationsmodel.cpp"
|
||||
|
||||
@@ -46,6 +46,9 @@ Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void boundingBoxChanged();
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
|
||||
|
||||
@@ -71,9 +71,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
MessageEventModel::MessageEventModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyAuthor, ReadMarkersRole});
|
||||
});
|
||||
}
|
||||
|
||||
NeoChatRoom *MessageEventModel::room() const
|
||||
@@ -95,12 +92,15 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
|
||||
m_currentRoom = room;
|
||||
Q_EMIT roomChanged();
|
||||
if (room) {
|
||||
m_lastReadEventIndex = QPersistentModelIndex(QModelIndex());
|
||||
room->setDisplayed();
|
||||
|
||||
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
|
||||
createEventObjects(&*event->viewAs<RoomMessageEvent>());
|
||||
if (const auto &roomMessageEvent = &*event->viewAs<RoomMessageEvent>()) {
|
||||
createEventObjects(roomMessageEvent);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
|
||||
@@ -120,9 +120,8 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
for (auto &&event : events) {
|
||||
const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||
|
||||
createEventObjects(message);
|
||||
|
||||
if (message != nullptr) {
|
||||
createEventObjects(message);
|
||||
if (NeoChatConfig::self()->showFancyEffects()) {
|
||||
QString planBody = message->plainBody();
|
||||
// snowflake
|
||||
@@ -158,8 +157,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
});
|
||||
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
|
||||
for (auto &event : events) {
|
||||
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||
createEventObjects(message);
|
||||
if (const auto &roomMessageEvent = dynamic_cast<RoomMessageEvent *>(event.get())) {
|
||||
createEventObjects(roomMessageEvent);
|
||||
}
|
||||
}
|
||||
if (rowCount() > 0) {
|
||||
rowBelowInserted = rowCount() - 1; // See #312
|
||||
@@ -228,7 +228,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
const auto eventIt = m_currentRoom->findInTimeline(eventId);
|
||||
if (eventIt != m_currentRoom->historyEdge()) {
|
||||
createEventObjects(static_cast<const RoomMessageEvent *>(&**eventIt));
|
||||
if (const auto &event = dynamic_cast<const RoomMessageEvent *>(&**eventIt)) {
|
||||
createEventObjects(event);
|
||||
}
|
||||
}
|
||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole, Qt::DisplayRole});
|
||||
});
|
||||
@@ -388,13 +390,7 @@ int MessageEventModel::rowCount(const QModelIndex &parent) const
|
||||
return 0;
|
||||
}
|
||||
|
||||
const auto firstIt = m_currentRoom->messageEvents().crbegin();
|
||||
if (firstIt != m_currentRoom->messageEvents().crend()) {
|
||||
const auto &firstEvt = **firstIt;
|
||||
return m_currentRoom->timelineSize() + (lastReadEventId != firstEvt.id() ? 1 : 0);
|
||||
} else {
|
||||
return m_currentRoom->timelineSize();
|
||||
}
|
||||
return int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize() + (m_lastReadEventIndex.isValid() ? 1 : 0);
|
||||
}
|
||||
|
||||
bool MessageEventModel::canFetchMore(const QModelIndex &parent) const
|
||||
@@ -421,7 +417,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
const auto row = idx.row();
|
||||
|
||||
if (!m_currentRoom || row < 0 || row >= int(m_currentRoom->pendingEvents().size()) + m_currentRoom->timelineSize()) {
|
||||
if (!m_currentRoom || row < 0 || row >= rowCount()) {
|
||||
return {};
|
||||
};
|
||||
|
||||
@@ -436,6 +432,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
const KFormat format;
|
||||
return format.formatRelativeDateTime(eventDate, QLocale::ShortFormat);
|
||||
}
|
||||
case SpecialMarksRole:
|
||||
// Check if all the earlier events in the timeline are hidden. If so hide this.
|
||||
for (auto r = row - 1; r >= 0; --r) {
|
||||
const auto specialMark = index(r).data(SpecialMarksRole);
|
||||
if (!(specialMark == EventStatus::Hidden || specialMark == EventStatus::Replaced)) {
|
||||
return EventStatus::Normal;
|
||||
}
|
||||
}
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -455,7 +460,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
|
||||
: i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason());
|
||||
}
|
||||
|
||||
return eventHandler.getRichBody();
|
||||
}
|
||||
|
||||
@@ -605,7 +609,10 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
if (data(i, SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
||||
return data(i, AuthorRole) != data(idx, AuthorRole) || data(i, DelegateTypeRole) == DelegateType::State
|
||||
|| data(i, TimeRole).toDateTime().msecsTo(data(idx, TimeRole).toDateTime()) > 600000
|
||||
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day();
|
||||
|| data(i, TimeRole).toDateTime().toLocalTime().date().day() != data(idx, TimeRole).toDateTime().toLocalTime().date().day()
|
||||
// FIXME: This should not be necessary; the proper fix is to calculate this role in MessageFilterModel with the knowledge about the filtered
|
||||
// events.
|
||||
|| data(i, IsRedactedRole).toBool();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -704,10 +711,6 @@ int MessageEventModel::eventIdToRow(const QString &eventID) const
|
||||
|
||||
void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
auto eventId = event->id();
|
||||
|
||||
EventHandler eventHandler;
|
||||
@@ -726,4 +729,12 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
|
||||
}
|
||||
}
|
||||
|
||||
bool MessageEventModel::event(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::ApplicationPaletteChange) {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyAuthor, ReadMarkersRole});
|
||||
}
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
#include "moc_messageeventmodel.cpp"
|
||||
|
||||
@@ -118,6 +118,9 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const;
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
private Q_SLOTS:
|
||||
int refreshEvent(const QString &eventId);
|
||||
void refreshRow(int row);
|
||||
|
||||
@@ -8,14 +8,15 @@
|
||||
#include "enums/delegatetype.h"
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "timelinemodel.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
MessageFilterModel::MessageFilterModel(QObject *parent, MessageEventModel *sourceMessageModel)
|
||||
MessageFilterModel::MessageFilterModel(QObject *parent, TimelineModel *sourceModel)
|
||||
: QSortFilterProxyModel(parent)
|
||||
{
|
||||
Q_ASSERT(sourceMessageModel);
|
||||
setSourceModel(sourceMessageModel);
|
||||
Q_ASSERT(sourceModel);
|
||||
setSourceModel(sourceModel);
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowStateEventChanged, this, [this] {
|
||||
invalidateFilter();
|
||||
@@ -117,35 +118,37 @@ QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
||||
chunks += QString();
|
||||
int count = 1;
|
||||
auto part = parts.takeFirst();
|
||||
chunks.last() += part;
|
||||
while (!parts.isEmpty() && parts.first() == part) {
|
||||
parts.removeFirst();
|
||||
count++;
|
||||
}
|
||||
if (count > 1 && uniqueAuthors.length() == 1) {
|
||||
chunks.last() += i18ncp("n times", " %1 time ", " %1 times ", count);
|
||||
}
|
||||
chunks.last() += i18ncp("%1: What's being done; %2: How often it is done.", " %1", " %1 %2 times", part, count);
|
||||
}
|
||||
chunks.removeDuplicates();
|
||||
QString text = QStringLiteral("<style>a {text-decoration: none;}</style>"); // There can be links in the event text so make sure all are styled.
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped());
|
||||
text += userText;
|
||||
text += chunks.takeFirst();
|
||||
|
||||
QString chunksText;
|
||||
chunksText += chunks.takeFirst();
|
||||
if (chunks.size() > 0) {
|
||||
while (chunks.size() > 1) {
|
||||
text += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
text += chunks.takeFirst();
|
||||
chunksText += i18nc("[action 1], [action 2 and/or action 3]", ", ");
|
||||
chunksText += chunks.takeFirst();
|
||||
}
|
||||
text += uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
text += chunks.takeFirst();
|
||||
chunksText +=
|
||||
uniqueAuthors.length() > 1 ? i18nc("[action 1, action 2] or [action 3]", " or ") : i18nc("[action 1, action 2] and [action 3]", " and ");
|
||||
chunksText += chunks.takeFirst();
|
||||
}
|
||||
return text;
|
||||
return i18nc(
|
||||
"userText (%1) is either a Matrix username if a single user sent all the states or n users if they were sent by multiple users."
|
||||
"chunksText (%2) is a list of comma separated actions for each of the state events in the group.",
|
||||
"<style>a {text-decoration: none;}</style>%1 %2",
|
||||
userText,
|
||||
chunksText);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "timelinemodel.h"
|
||||
|
||||
/**
|
||||
* @class MessageFilterModel
|
||||
@@ -36,7 +37,7 @@ public:
|
||||
LastRole, // Keep this last
|
||||
};
|
||||
|
||||
explicit MessageFilterModel(QObject *parent = nullptr, MessageEventModel *sourceMessageModel = nullptr);
|
||||
explicit MessageFilterModel(QObject *parent = nullptr, TimelineModel *sourceModel = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Custom filter function to remove hidden messages.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user