Compare commits
60 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
96a477f91c | ||
|
|
5a6b0f756d | ||
|
|
dbbf975f7a | ||
|
|
e06c2d2f93 | ||
|
|
5dea17b616 | ||
|
|
63b6d7ebe0 | ||
|
|
652106e6a1 | ||
|
|
3e158b3e60 | ||
|
|
b9ec33dd94 | ||
|
|
69bd1202ba | ||
|
|
f75c09e130 | ||
|
|
683d216f44 | ||
|
|
c10bcf1764 | ||
|
|
9e2bf0da26 | ||
|
|
51f7de117d | ||
|
|
f361f4e2d8 | ||
|
|
acfb5ab834 | ||
|
|
8bc4500ce1 | ||
|
|
b96d3dde46 | ||
|
|
fad381c36f | ||
|
|
ad083f64b1 | ||
|
|
5c78b23cc2 | ||
|
|
2202063641 | ||
|
|
5be15ffa6a | ||
|
|
a0bafe53a0 | ||
|
|
1a39ce9585 | ||
|
|
4a809d57f7 | ||
|
|
c01e42c972 | ||
|
|
6c56f7f4ef | ||
|
|
6d8d5a82c2 | ||
|
|
daa27b0333 | ||
|
|
f6edf0e4cc | ||
|
|
356e8eefe0 | ||
|
|
7ad362225f | ||
|
|
d623a8c826 | ||
|
|
b3f0d110d9 | ||
|
|
612b5d7f47 | ||
|
|
7e9f206348 | ||
|
|
d5f4a3dd64 | ||
|
|
e807ad9908 | ||
|
|
2d8ad834a7 | ||
|
|
d82dfc7a5b | ||
|
|
1e1e54d4bd | ||
|
|
9043b1c7a1 | ||
|
|
0739f4c661 | ||
|
|
29321aeaf3 | ||
|
|
9fe44515a7 | ||
|
|
4c3d7ab011 | ||
|
|
d02eee6daa | ||
|
|
a613baa148 | ||
|
|
4e141e05f0 | ||
|
|
134f1d2ae5 | ||
|
|
9ec3e5b2cc | ||
|
|
15d066b3ed | ||
|
|
7694f6c464 | ||
|
|
46da4f9777 | ||
|
|
44e0ef43ac | ||
|
|
5b79371c0a | ||
|
|
3caa5ad2ed | ||
|
|
a74d78439c |
@@ -2,7 +2,7 @@
|
|||||||
"id": "org.kde.neochat",
|
"id": "org.kde.neochat",
|
||||||
"branch": "master",
|
"branch": "master",
|
||||||
"runtime": "org.kde.Platform",
|
"runtime": "org.kde.Platform",
|
||||||
"runtime-version": "5.15-22.08",
|
"runtime-version": "6.6-kf6preview",
|
||||||
"sdk": "org.kde.Sdk",
|
"sdk": "org.kde.Sdk",
|
||||||
"command": "neochat",
|
"command": "neochat",
|
||||||
"tags": [
|
"tags": [
|
||||||
@@ -12,7 +12,7 @@
|
|||||||
"finish-args": [
|
"finish-args": [
|
||||||
"--share=network",
|
"--share=network",
|
||||||
"--share=ipc",
|
"--share=ipc",
|
||||||
"--socket=x11",
|
"--socket=fallback-x11",
|
||||||
"--socket=wayland",
|
"--socket=wayland",
|
||||||
"--device=dri",
|
"--device=dri",
|
||||||
"--filesystem=xdg-download",
|
"--filesystem=xdg-download",
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "kquickimageeditor",
|
"name": "kquickimageeditor",
|
||||||
|
"config-opts": [ "-DBUILD_WITH_QT6=ON" ],
|
||||||
"buildsystem": "cmake-ninja",
|
"buildsystem": "cmake-ninja",
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
@@ -85,8 +86,8 @@
|
|||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"type": "archive",
|
"type": "archive",
|
||||||
"url": "https://github.com/frankosterfeld/qtkeychain/archive/v0.13.2.tar.gz",
|
"url": "https://github.com/frankosterfeld/qtkeychain/archive/0.14.2.tar.gz",
|
||||||
"sha256": "20beeb32de7c4eb0af9039b21e18370faf847ac8697ab3045906076afbc4caa5",
|
"sha256": "cf2e972b783ba66334a79a30f6b3a1ea794a1dc574d6c3bebae5ffd2f0399571",
|
||||||
"x-checker-data": {
|
"x-checker-data": {
|
||||||
"type": "anitya",
|
"type": "anitya",
|
||||||
"project-id": 4138,
|
"project-id": 4138,
|
||||||
@@ -96,6 +97,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"config-opts": [
|
"config-opts": [
|
||||||
|
"-DBUILD_WITH_QT6=ON",
|
||||||
"-DCMAKE_INSTALL_LIBDIR=/app/lib",
|
"-DCMAKE_INSTALL_LIBDIR=/app/lib",
|
||||||
"-DLIB_INSTALL_DIR=/app/lib",
|
"-DLIB_INSTALL_DIR=/app/lib",
|
||||||
"-DBUILD_TRANSLATIONS=NO"
|
"-DBUILD_TRANSLATIONS=NO"
|
||||||
@@ -113,6 +115,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"config-opts": [
|
"config-opts": [
|
||||||
|
"-DBUILD_WITH_QT6=ON",
|
||||||
"-DQuotient_ENABLE_E2EE=ON",
|
"-DQuotient_ENABLE_E2EE=ON",
|
||||||
"-DBUILD_TESTING=OFF"
|
"-DBUILD_TESTING=OFF"
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -9,5 +9,5 @@ include:
|
|||||||
- /gitlab-templates/linux-qt6.yml
|
- /gitlab-templates/linux-qt6.yml
|
||||||
- /gitlab-templates/windows-qt6.yml
|
- /gitlab-templates/windows-qt6.yml
|
||||||
- /gitlab-templates/freebsd-qt6.yml
|
- /gitlab-templates/freebsd-qt6.yml
|
||||||
# - /gitlab-templates/flatpak.yml
|
- /gitlab-templates/flatpak.yml
|
||||||
- /gitlab-templates/craft-android-qt6-apks.yml
|
- /gitlab-templates/craft-android-qt6-apks.yml
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ cmake_minimum_required(VERSION 3.16)
|
|||||||
# KDE Applications version, managed by release script.
|
# KDE Applications version, managed by release script.
|
||||||
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||||
set(RELEASE_SERVICE_VERSION_MINOR "01")
|
set(RELEASE_SERVICE_VERSION_MINOR "01")
|
||||||
set(RELEASE_SERVICE_VERSION_MICRO "85")
|
set(RELEASE_SERVICE_VERSION_MICRO "90")
|
||||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||||
|
|
||||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||||
|
|||||||
@@ -58,3 +58,27 @@ ecm_add_test(
|
|||||||
LINK_LIBRARIES neochat Qt::Test
|
LINK_LIBRARIES neochat Qt::Test
|
||||||
TEST_NAME actionshandlertest
|
TEST_NAME actionshandlertest
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ecm_add_test(
|
||||||
|
windowcontrollertest.cpp
|
||||||
|
LINK_LIBRARIES neochat Qt::Test
|
||||||
|
TEST_NAME windowcontrollertest
|
||||||
|
)
|
||||||
|
|
||||||
|
ecm_add_test(
|
||||||
|
pollhandlertest.cpp
|
||||||
|
LINK_LIBRARIES neochat Qt::Test
|
||||||
|
TEST_NAME pollhandlertest
|
||||||
|
)
|
||||||
|
|
||||||
|
ecm_add_test(
|
||||||
|
reactionmodeltest.cpp
|
||||||
|
LINK_LIBRARIES neochat Qt::Test
|
||||||
|
TEST_NAME reactionmodeltest
|
||||||
|
)
|
||||||
|
|
||||||
|
ecm_add_test(
|
||||||
|
linkpreviewertest.cpp
|
||||||
|
LINK_LIBRARIES neochat Qt::Test
|
||||||
|
TEST_NAME linkpreviewertest
|
||||||
|
)
|
||||||
|
|||||||
14
autotests/data/test-invalidmatrixtolink-event.json
Normal file
14
autotests/data/test-invalidmatrixtolink-event.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "https://matrix.to/#/@alice:example.org",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$validlink1:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!test:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
14
autotests/data/test-invalidmxclink-event.json
Normal file
14
autotests/data/test-invalidmxclink-event.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$validlink1:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!test:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
14
autotests/data/test-invalidnospacelink-event.json
Normal file
14
autotests/data/test-invalidnospacelink-event.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "testhttps://kde.org",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$validlink1:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!test:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
24
autotests/data/test-linkpreviewerintial-sync.json
Normal file
24
autotests/data/test-linkpreviewerintial-sync.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "https://kde.org",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "https://kde.org",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1704648567967,
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 112
|
||||||
|
},
|
||||||
|
"event_id": "$validlink:example.org",
|
||||||
|
"room_id": "!test:example.org"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limited": true,
|
||||||
|
"prev_batch": "t34-23535_0_0"
|
||||||
|
}
|
||||||
|
}
|
||||||
35
autotests/data/test-linkpreviewerreplace-sync.json
Normal file
35
autotests/data/test-linkpreviewerreplace-sync.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "* ",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "no link",
|
||||||
|
"m.new_content": {
|
||||||
|
"body": "",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "no link",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"m.relates_to": {
|
||||||
|
"event_id": "$validlink:example.org",
|
||||||
|
"rel_type": "m.replace"
|
||||||
|
},
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"type": "m.room.message"
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1704648614969,
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 65
|
||||||
|
},
|
||||||
|
"event_id": "$nolink:example.org",
|
||||||
|
"room_id": "!test:example.org"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limited": true,
|
||||||
|
"prev_batch": "t34-23535_0_0"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
autotests/data/test-multiplelink-event.json
Normal file
14
autotests/data/test-multiplelink-event.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "www.example.org https://kde.org",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$validlink1:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!test:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
38
autotests/data/test-pollhandlerstart-sync.json
Normal file
38
autotests/data/test-pollhandlerstart-sync.json
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
{
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"org.matrix.msc1767.text": "test\n1. option1\n2. option 2",
|
||||||
|
"org.matrix.msc3381.poll.start": {
|
||||||
|
"answers": [
|
||||||
|
{
|
||||||
|
"id": "option1",
|
||||||
|
"org.matrix.msc1767.text": "option1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "option2",
|
||||||
|
"org.matrix.msc1767.text": "option2"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"kind": "org.matrix.msc3381.poll.disclosed",
|
||||||
|
"max_selections": 1,
|
||||||
|
"question": {
|
||||||
|
"body": "test",
|
||||||
|
"msgtype": "m.text",
|
||||||
|
"org.matrix.msc1767.text": "test"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"event_id": "$153456789:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "org.matrix.msc3381.poll.start",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1232
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
44
autotests/data/test-reactionmodel-extra-sync.json
Normal file
44
autotests/data/test-reactionmodel-extra-sync.json
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
{
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"m.relates_to": {
|
||||||
|
"event_id": "$153456789:example.org",
|
||||||
|
"key": "👍",
|
||||||
|
"rel_type": "m.annotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1690322545183,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@bob:example.org",
|
||||||
|
"type": "m.reaction",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 390159121
|
||||||
|
},
|
||||||
|
"event_id": "$163456790:example.org",
|
||||||
|
"age": 390159121
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"m.relates_to": {
|
||||||
|
"event_id": "$153456789:example.org",
|
||||||
|
"key": "😆",
|
||||||
|
"rel_type": "m.annotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1690322545184,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@bob:example.org",
|
||||||
|
"type": "m.reaction",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 390159122
|
||||||
|
},
|
||||||
|
"event_id": "$163456791:example.org",
|
||||||
|
"age": 390159122
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limited": true,
|
||||||
|
"prev_batch": "t34-23535_0_0"
|
||||||
|
}
|
||||||
|
}
|
||||||
204
autotests/data/test-reactionmodel-sync.json
Normal file
204
autotests/data/test-reactionmodel-sync.json
Normal file
@@ -0,0 +1,204 @@
|
|||||||
|
{
|
||||||
|
"account_data": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"tags": {
|
||||||
|
"u.work": {
|
||||||
|
"order": 0.9
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"custom_config_key": "custom_config_value"
|
||||||
|
},
|
||||||
|
"type": "org.example.custom.room.config"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ephemeral": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"user_ids": [
|
||||||
|
"@alice:matrix.org",
|
||||||
|
"@bob:example.com"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"type": "m.typing"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$153456789:example.org": {
|
||||||
|
"m.read": {
|
||||||
|
"@alice:matrix.org": {
|
||||||
|
"ts": 1436451550453
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$1532735824654:example.org": {
|
||||||
|
"m.read": {
|
||||||
|
"@bob:example.com": {
|
||||||
|
"ts": 1436451550453
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$1532735824654:example.org": {
|
||||||
|
"m.read": {
|
||||||
|
"@tim:example.com": {
|
||||||
|
"ts": 1436451550454
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$1532735824654:example.org": {
|
||||||
|
"m.read": {
|
||||||
|
"@jeff:example.com": {
|
||||||
|
"ts": 1436451550455
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$1532735824654:example.org": {
|
||||||
|
"m.read": {
|
||||||
|
"@tina:example.com": {
|
||||||
|
"ts": 1436451550456
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$1532735824654:example.org": {
|
||||||
|
"m.read": {
|
||||||
|
"@sally:example.com": {
|
||||||
|
"ts": 1436451550457
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$1532735824654:example.org": {
|
||||||
|
"m.read": {
|
||||||
|
"@fred:example.com": {
|
||||||
|
"ts": 1436451550458
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"state": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||||
|
"displayname": "Alice Margatroid",
|
||||||
|
"membership": "join",
|
||||||
|
"reason": "Looking for support"
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"state_key": "@alice:example.org",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"displayname": "Look\nat\nme\nI\nput\nnewlines\nin\nmy\ndisplay name",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSh:example.org",
|
||||||
|
"origin_server_ts": 1432735824659,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@newline:example.org",
|
||||||
|
"state_key": "@newline:example.org",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 12345
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"summary": {
|
||||||
|
"m.heroes": [
|
||||||
|
"@alice:example.com",
|
||||||
|
"@bob:example.com"
|
||||||
|
],
|
||||||
|
"m.invited_member_count": 0,
|
||||||
|
"m.joined_member_count": 2
|
||||||
|
},
|
||||||
|
"timeline": {
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "This is an example\ntext message",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<b>This is an example<br>text message</b>",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$153456789:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1232
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"m.relates_to": {
|
||||||
|
"event_id": "$153456789:example.org",
|
||||||
|
"key": "👍",
|
||||||
|
"rel_type": "m.annotation"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"origin_server_ts": 1690322545182,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@alice:matrix.org",
|
||||||
|
"type": "m.reaction",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 390159120
|
||||||
|
},
|
||||||
|
"event_id": "$163456789:example.org",
|
||||||
|
"age": 390159120
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"limited": true,
|
||||||
|
"prev_batch": "t34-23535_0_0"
|
||||||
|
}
|
||||||
|
}
|
||||||
14
autotests/data/test-validplainlink-event.json
Normal file
14
autotests/data/test-validplainlink-event.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "https://kde.org",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$validlink1:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!test:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
14
autotests/data/test-validplainwwwlink-event.json
Normal file
14
autotests/data/test-validplainwwwlink-event.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "www.example.org",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$validlink1:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!test:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
16
autotests/data/test-validrichlink-event.json
Normal file
16
autotests/data/test-validrichlink-event.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"body": "[Rich Link](https://kde.org)",
|
||||||
|
"format": "org.matrix.custom.html",
|
||||||
|
"formatted_body": "<a href=\"https://kde.org\">Rich Link</a>",
|
||||||
|
"msgtype": "m.text"
|
||||||
|
},
|
||||||
|
"event_id": "$validlink1:example.org",
|
||||||
|
"origin_server_ts": 1432735824654,
|
||||||
|
"room_id": "!test:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"type": "m.room.message",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -65,10 +65,6 @@ private Q_SLOTS:
|
|||||||
void nullSubtitle();
|
void nullSubtitle();
|
||||||
void mediaInfo();
|
void mediaInfo();
|
||||||
void nullMediaInfo();
|
void nullMediaInfo();
|
||||||
void linkPreviewer();
|
|
||||||
void nullLinkPreviewer();
|
|
||||||
void reactions();
|
|
||||||
void nullReactions();
|
|
||||||
void hasReply();
|
void hasReply();
|
||||||
void nullHasReply();
|
void nullHasReply();
|
||||||
void replyId();
|
void replyId();
|
||||||
@@ -401,45 +397,6 @@ void EventHandlerTest::nullMediaInfo()
|
|||||||
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
|
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandlerTest::linkPreviewer()
|
|
||||||
{
|
|
||||||
auto event = room->messageEvents().at(2).get();
|
|
||||||
eventHandler.setEvent(event);
|
|
||||||
|
|
||||||
QCOMPARE(eventHandler.getLinkPreviewer()->url(), QUrl("https://kde.org"_ls));
|
|
||||||
|
|
||||||
event = room->messageEvents().at(0).get();
|
|
||||||
eventHandler.setEvent(event);
|
|
||||||
|
|
||||||
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();
|
|
||||||
eventHandler.setEvent(event);
|
|
||||||
|
|
||||||
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()
|
void EventHandlerTest::hasReply()
|
||||||
{
|
{
|
||||||
auto event = room->messageEvents().at(5).get();
|
auto event = room->messageEvents().at(5).get();
|
||||||
|
|||||||
104
autotests/linkpreviewertest.cpp
Normal file
104
autotests/linkpreviewertest.cpp
Normal file
@@ -0,0 +1,104 @@
|
|||||||
|
// 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 "linkpreviewer.h"
|
||||||
|
|
||||||
|
#include <Quotient/events/roommessageevent.h>
|
||||||
|
#include <Quotient/quotient_common.h>
|
||||||
|
#include <Quotient/syncdata.h>
|
||||||
|
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
|
#include "testutils.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
class LinkPreviewerTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
Connection *connection = nullptr;
|
||||||
|
TestUtils::TestRoom *room = nullptr;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void initTestCase();
|
||||||
|
|
||||||
|
void linkPreviewsMatch_data();
|
||||||
|
void linkPreviewsMatch();
|
||||||
|
|
||||||
|
void linkPreviewsReject_data();
|
||||||
|
void linkPreviewsReject();
|
||||||
|
|
||||||
|
void editedLink();
|
||||||
|
};
|
||||||
|
|
||||||
|
void LinkPreviewerTest::initTestCase()
|
||||||
|
{
|
||||||
|
connection = Connection::makeMockConnection(QStringLiteral("@bob:example.org"));
|
||||||
|
room = new TestUtils::TestRoom(connection, QStringLiteral("!test:example.org"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::linkPreviewsMatch_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("eventSource");
|
||||||
|
QTest::addColumn<QUrl>("testOutputLink");
|
||||||
|
|
||||||
|
QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
|
||||||
|
QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
|
||||||
|
QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
|
||||||
|
QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::linkPreviewsMatch()
|
||||||
|
{
|
||||||
|
QFETCH(QString, eventSource);
|
||||||
|
QFETCH(QUrl, testOutputLink);
|
||||||
|
|
||||||
|
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
|
||||||
|
auto linkPreviewer = LinkPreviewer(room, event.get());
|
||||||
|
|
||||||
|
QCOMPARE(linkPreviewer.empty(), false);
|
||||||
|
QCOMPARE(linkPreviewer.url(), testOutputLink);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::linkPreviewsReject_data()
|
||||||
|
{
|
||||||
|
QTest::addColumn<QString>("eventSource");
|
||||||
|
|
||||||
|
QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
|
||||||
|
QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
|
||||||
|
QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::linkPreviewsReject()
|
||||||
|
{
|
||||||
|
QFETCH(QString, eventSource);
|
||||||
|
|
||||||
|
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
|
||||||
|
auto linkPreviewer = LinkPreviewer(room, event.get());
|
||||||
|
|
||||||
|
QCOMPARE(linkPreviewer.empty(), true);
|
||||||
|
QCOMPARE(linkPreviewer.url(), QUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
void LinkPreviewerTest::editedLink()
|
||||||
|
{
|
||||||
|
room->syncNewEvents(QStringLiteral("test-linkpreviewerintial-sync.json"));
|
||||||
|
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
|
||||||
|
auto linkPreviewer = LinkPreviewer(room, event);
|
||||||
|
|
||||||
|
QCOMPARE(linkPreviewer.empty(), false);
|
||||||
|
QCOMPARE(linkPreviewer.url(), QUrl("https://kde.org"_ls));
|
||||||
|
|
||||||
|
room->syncNewEvents(QStringLiteral("test-linkpreviewerreplace-sync.json"));
|
||||||
|
|
||||||
|
QCOMPARE(linkPreviewer.empty(), true);
|
||||||
|
QCOMPARE(linkPreviewer.url(), QUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(LinkPreviewerTest)
|
||||||
|
#include "linkpreviewertest.moc"
|
||||||
70
autotests/pollhandlertest.cpp
Normal file
70
autotests/pollhandlertest.cpp
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
// 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 "events/pollevent.h"
|
||||||
|
#include "pollhandler.h"
|
||||||
|
#include "testutils.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
class PollHandlerTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
Connection *connection = nullptr;
|
||||||
|
TestUtils::TestRoom *room = nullptr;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void initTestCase();
|
||||||
|
void nullObject();
|
||||||
|
void poll();
|
||||||
|
};
|
||||||
|
|
||||||
|
void PollHandlerTest::initTestCase()
|
||||||
|
{
|
||||||
|
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||||
|
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), "test-pollhandlerstart-sync.json"_ls);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Basically don't crash.
|
||||||
|
void PollHandlerTest::nullObject()
|
||||||
|
{
|
||||||
|
auto pollHandler = PollHandler();
|
||||||
|
|
||||||
|
QCOMPARE(pollHandler.hasEnded(), false);
|
||||||
|
QCOMPARE(pollHandler.answerCount(), 0);
|
||||||
|
QCOMPARE(pollHandler.question(), QString());
|
||||||
|
QCOMPARE(pollHandler.options(), QJsonArray());
|
||||||
|
QCOMPARE(pollHandler.answers(), QJsonObject());
|
||||||
|
QCOMPARE(pollHandler.counts(), QJsonObject());
|
||||||
|
QCOMPARE(pollHandler.kind(), QString());
|
||||||
|
}
|
||||||
|
|
||||||
|
void PollHandlerTest::poll()
|
||||||
|
{
|
||||||
|
auto startEvent = eventCast<const PollStartEvent>(room->messageEvents().at(0).get());
|
||||||
|
auto pollHandler = PollHandler(room, startEvent);
|
||||||
|
|
||||||
|
auto options = QJsonArray{QJsonObject{{"id"_ls, "option1"_ls}, {"org.matrix.msc1767.text"_ls, "option1"_ls}},
|
||||||
|
QJsonObject{{"id"_ls, "option2"_ls}, {"org.matrix.msc1767.text"_ls, "option2"_ls}}};
|
||||||
|
|
||||||
|
QCOMPARE(pollHandler.hasEnded(), false);
|
||||||
|
QCOMPARE(pollHandler.answerCount(), 0);
|
||||||
|
QCOMPARE(pollHandler.question(), QStringLiteral("test"));
|
||||||
|
QCOMPARE(pollHandler.options(), options);
|
||||||
|
QCOMPARE(pollHandler.answers(), QJsonObject());
|
||||||
|
QCOMPARE(pollHandler.counts(), QJsonObject());
|
||||||
|
QCOMPARE(pollHandler.kind(), QStringLiteral("org.matrix.msc3381.poll.disclosed"));
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_GUILESS_MAIN(PollHandlerTest)
|
||||||
|
#include "pollhandlertest.moc"
|
||||||
83
autotests/reactionmodeltest.cpp
Normal file
83
autotests/reactionmodeltest.cpp
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
// 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 "models/reactionmodel.h"
|
||||||
|
|
||||||
|
#include <Quotient/events/roommessageevent.h>
|
||||||
|
|
||||||
|
#include "testutils.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
class ReactionModelTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private:
|
||||||
|
Connection *connection = nullptr;
|
||||||
|
TestUtils::TestRoom *room = nullptr;
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void initTestCase();
|
||||||
|
|
||||||
|
void nullModel();
|
||||||
|
void basicReaction();
|
||||||
|
void newReaction();
|
||||||
|
};
|
||||||
|
|
||||||
|
void ReactionModelTest::initTestCase()
|
||||||
|
{
|
||||||
|
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||||
|
room = new TestUtils::TestRoom(connection, QStringLiteral("#myroom:kde.org"), QLatin1String("test-reactionmodel-sync.json"));
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactionModelTest::nullModel()
|
||||||
|
{
|
||||||
|
auto model = ReactionModel(nullptr, nullptr);
|
||||||
|
|
||||||
|
QCOMPARE(model.rowCount(), 0);
|
||||||
|
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QVariant());
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactionModelTest::basicReaction()
|
||||||
|
{
|
||||||
|
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
|
||||||
|
auto model = ReactionModel(event, room);
|
||||||
|
|
||||||
|
QCOMPARE(model.rowCount(), 1);
|
||||||
|
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QStringLiteral("<span style=\"font-family: 'emoji';\">👍</span>"));
|
||||||
|
QCOMPARE(model.data(model.index(0), ReactionModel::ReactionRole), QStringLiteral("👍"));
|
||||||
|
QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole),
|
||||||
|
QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
||||||
|
auto authorList = QVariantList{room->getUser(room->user(QStringLiteral("@alice:matrix.org")))};
|
||||||
|
QCOMPARE(model.data(model.index(0), ReactionModel::AuthorsRole), authorList);
|
||||||
|
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalUser), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReactionModelTest::newReaction()
|
||||||
|
{
|
||||||
|
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
|
||||||
|
auto model = new ReactionModel(event, room);
|
||||||
|
|
||||||
|
QCOMPARE(model->rowCount(), 1);
|
||||||
|
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
|
||||||
|
QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
||||||
|
|
||||||
|
QSignalSpy spy(model, SIGNAL(modelReset()));
|
||||||
|
|
||||||
|
room->syncNewEvents(QLatin1String("test-reactionmodel-extra-sync.json"));
|
||||||
|
QCOMPARE(model->rowCount(), 2);
|
||||||
|
QCOMPARE(spy.count(), 2); // Once for each of the 2 new reactions.
|
||||||
|
QCOMPARE(model->data(model->index(1), ReactionModel::ReactionRole), QStringLiteral("😆"));
|
||||||
|
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
|
||||||
|
QStringLiteral("@alice:matrix.org and @bob:example.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
||||||
|
|
||||||
|
delete model;
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(ReactionModelTest)
|
||||||
|
#include "reactionmodeltest.moc"
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
// 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
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#include <Quotient/events/event.h>
|
||||||
#include <Quotient/syncdata.h>
|
#include <Quotient/syncdata.h>
|
||||||
|
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
@@ -38,4 +39,17 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<Quotient::EventClass EventT>
|
||||||
|
inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFileName)
|
||||||
|
{
|
||||||
|
if (!eventFileName.isEmpty()) {
|
||||||
|
QFile testEventFile;
|
||||||
|
testEventFile.setFileName(QLatin1String(DATA_DIR) + u'/' + eventFileName);
|
||||||
|
testEventFile.open(QIODevice::ReadOnly);
|
||||||
|
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
|
||||||
|
return Quotient::loadEvent<EventT>(testSyncJson);
|
||||||
|
}
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,11 +64,6 @@ private Q_SLOTS:
|
|||||||
void receiveRichEdited();
|
void receiveRichEdited();
|
||||||
void receiveLineSeparator();
|
void receiveLineSeparator();
|
||||||
void receiveRichCodeUrl();
|
void receiveRichCodeUrl();
|
||||||
|
|
||||||
void linkPreviewsMatch_data();
|
|
||||||
void linkPreviewsMatch();
|
|
||||||
void linkPreviewsReject_data();
|
|
||||||
void linkPreviewsReject();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void TextHandlerTest::initTestCase()
|
void TextHandlerTest::initTestCase()
|
||||||
@@ -523,53 +518,6 @@ void TextHandlerTest::receiveLineSeparator()
|
|||||||
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
|
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void TextHandlerTest::linkPreviewsMatch_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("testInputString");
|
|
||||||
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
|
||||||
|
|
||||||
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org"_ls)});
|
|
||||||
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org"_ls)});
|
|
||||||
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org"_ls)});
|
|
||||||
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
|
|
||||||
<< QList<QUrl>({
|
|
||||||
QUrl("https://kde.org"_ls),
|
|
||||||
QUrl("www.example.org"_ls),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::linkPreviewsMatch()
|
|
||||||
{
|
|
||||||
QFETCH(QString, testInputString);
|
|
||||||
QFETCH(QList<QUrl>, testOutputLinks);
|
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
|
||||||
testTextHandler.setData(testInputString);
|
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::linkPreviewsReject_data()
|
|
||||||
{
|
|
||||||
QTest::addColumn<QString>("testInputString");
|
|
||||||
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
|
||||||
|
|
||||||
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList<QUrl>();
|
|
||||||
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList<QUrl>();
|
|
||||||
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList<QUrl>();
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::linkPreviewsReject()
|
|
||||||
{
|
|
||||||
QFETCH(QString, testInputString);
|
|
||||||
QFETCH(QList<QUrl>, testOutputLinks);
|
|
||||||
|
|
||||||
TextHandler testTextHandler;
|
|
||||||
testTextHandler.setData(testInputString);
|
|
||||||
|
|
||||||
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
|
|
||||||
}
|
|
||||||
|
|
||||||
void TextHandlerTest::receiveRichCodeUrl()
|
void TextHandlerTest::receiveRichCodeUrl()
|
||||||
{
|
{
|
||||||
auto input = QStringLiteral("<code>https://kde.org</code>");
|
auto input = QStringLiteral("<code>https://kde.org</code>");
|
||||||
|
|||||||
102
autotests/windowcontrollertest.cpp
Normal file
102
autotests/windowcontrollertest.cpp
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
// 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 <QQmlApplicationEngine>
|
||||||
|
#include <QTest>
|
||||||
|
#include <QWindow>
|
||||||
|
|
||||||
|
#include <KConfig>
|
||||||
|
#include <KSharedConfig>
|
||||||
|
#include <KWindowConfig>
|
||||||
|
|
||||||
|
#include "windowcontroller.h"
|
||||||
|
|
||||||
|
class WindowControllerTest : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
private Q_SLOTS:
|
||||||
|
void nullWindow();
|
||||||
|
void geometry();
|
||||||
|
void showAndRaise();
|
||||||
|
void toggle();
|
||||||
|
|
||||||
|
void cleanup();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Basically don't crash when no window is set.
|
||||||
|
void WindowControllerTest::nullWindow()
|
||||||
|
{
|
||||||
|
auto &instance = WindowController::instance();
|
||||||
|
QCOMPARE(instance.window(), nullptr);
|
||||||
|
|
||||||
|
instance.restoreGeometry();
|
||||||
|
instance.saveGeometry();
|
||||||
|
instance.showAndRaiseWindow({});
|
||||||
|
instance.toggleWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowControllerTest::geometry()
|
||||||
|
{
|
||||||
|
auto &instance = WindowController::instance();
|
||||||
|
|
||||||
|
QWindow window;
|
||||||
|
window.setGeometry(0, 0, 200, 200);
|
||||||
|
instance.setWindow(&window);
|
||||||
|
QCOMPARE(instance.window(), &window);
|
||||||
|
|
||||||
|
instance.saveGeometry();
|
||||||
|
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||||
|
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||||
|
QCOMPARE(KWindowConfig::hasSavedWindowSize(windowGroup), true);
|
||||||
|
|
||||||
|
window.setGeometry(0, 0, 400, 400);
|
||||||
|
QCOMPARE(window.geometry(), QRect(0, 0, 400, 400));
|
||||||
|
instance.restoreGeometry();
|
||||||
|
QCOMPARE(window.geometry(), QRect(0, 0, 200, 200));
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowControllerTest::showAndRaise()
|
||||||
|
{
|
||||||
|
auto &instance = WindowController::instance();
|
||||||
|
QWindow window;
|
||||||
|
instance.setWindow(&window);
|
||||||
|
QCOMPARE(window.isVisible(), false);
|
||||||
|
|
||||||
|
instance.showAndRaiseWindow({});
|
||||||
|
QCOMPARE(window.isVisible(), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowControllerTest::cleanup()
|
||||||
|
{
|
||||||
|
auto &instance = WindowController::instance();
|
||||||
|
instance.setWindow(nullptr);
|
||||||
|
QCOMPARE(instance.window(), nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
void WindowControllerTest::toggle()
|
||||||
|
{
|
||||||
|
auto &instance = WindowController::instance();
|
||||||
|
QWindow window;
|
||||||
|
instance.setWindow(&window);
|
||||||
|
QCOMPARE(window.isVisible(), false);
|
||||||
|
instance.toggleWindow();
|
||||||
|
QCOMPARE(window.isVisible(), true);
|
||||||
|
instance.toggleWindow();
|
||||||
|
QCOMPARE(window.isVisible(), false);
|
||||||
|
|
||||||
|
// A window is classed as visible by qt when minimized but to the user this is not visible.
|
||||||
|
// So in this case we expect to show it even though visibility is technically true.
|
||||||
|
window.setVisibility(QWindow::Minimized);
|
||||||
|
QCOMPARE(window.windowState(), Qt::WindowMinimized);
|
||||||
|
QCOMPARE(window.isVisible(), true);
|
||||||
|
instance.toggleWindow();
|
||||||
|
QCOMPARE(window.windowState(), Qt::WindowNoState);
|
||||||
|
QCOMPARE(window.isVisible(), true);
|
||||||
|
instance.toggleWindow();
|
||||||
|
QCOMPARE(window.windowState(), Qt::WindowNoState);
|
||||||
|
QCOMPARE(window.isVisible(), false);
|
||||||
|
}
|
||||||
|
|
||||||
|
QTEST_MAIN(WindowControllerTest)
|
||||||
|
#include "windowcontrollertest.moc"
|
||||||
@@ -52,6 +52,7 @@
|
|||||||
<summary xml:lang="ar">دردش مع أصدقائك على ماتركس</summary>
|
<summary xml:lang="ar">دردش مع أصدقائك على ماتركس</summary>
|
||||||
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
|
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
|
||||||
<summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
|
<summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
|
||||||
|
<summary xml:lang="cs">Mluvte se svými přáteli na Matrixu</summary>
|
||||||
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
|
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
|
||||||
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
|
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
|
||||||
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
|
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
|
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
|
||||||
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
|
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
|
||||||
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
|
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
|
||||||
|
<summary xml:lang="zh-CN">在 Matrix 上与朋友聊天</summary>
|
||||||
<summary xml:lang="zh-TW">在 Matrix 上與您的朋友聊天</summary>
|
<summary xml:lang="zh-TW">在 Matrix 上與您的朋友聊天</summary>
|
||||||
<description>
|
<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
|
<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
|
||||||
@@ -231,8 +233,8 @@ to provide a convergent experience across multiple platforms.</p>
|
|||||||
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
|
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
|
||||||
</ul>
|
</ul>
|
||||||
</description>
|
</description>
|
||||||
<url type="homepage">https://apps.kde.org/neochat/</url>
|
<url type="homepage">https://apps.kde.org/neochat</url>
|
||||||
<url type="bugtracker">https://bugs.kde.org/buglist.cgi?component=General&product=NeoChat</url>
|
<url type="bugtracker">https://bugs.kde.org/enter_bug.cgi?product=NeoChat</url>
|
||||||
<categories>
|
<categories>
|
||||||
<category>Network</category>
|
<category>Network</category>
|
||||||
</categories>
|
</categories>
|
||||||
@@ -327,6 +329,7 @@ to provide a convergent experience across multiple platforms.</p>
|
|||||||
<caption xml:lang="ar">شاشة الدخول</caption>
|
<caption xml:lang="ar">شاشة الدخول</caption>
|
||||||
<caption xml:lang="ca">Pantalla d'inici de sessió</caption>
|
<caption xml:lang="ca">Pantalla d'inici de sessió</caption>
|
||||||
<caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption>
|
<caption xml:lang="ca-valencia">Pantalla d'inici de sessió</caption>
|
||||||
|
<caption xml:lang="cs">Přihlašovací obrazovka</caption>
|
||||||
<caption xml:lang="eo">Ensaluta ekrano</caption>
|
<caption xml:lang="eo">Ensaluta ekrano</caption>
|
||||||
<caption xml:lang="es">Pantalla de inicio de sesión</caption>
|
<caption xml:lang="es">Pantalla de inicio de sesión</caption>
|
||||||
<caption xml:lang="eu">Saio-hasteko pantaila</caption>
|
<caption xml:lang="eu">Saio-hasteko pantaila</caption>
|
||||||
|
|||||||
486
po/ar/neochat.po
486
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
486
po/az/neochat.po
486
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
489
po/ca/neochat.po
489
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
573
po/cs/neochat.po
573
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
479
po/da/neochat.po
479
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
487
po/de/neochat.po
487
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/el/neochat.po
486
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
490
po/eo/neochat.po
490
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/es/neochat.po
486
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
487
po/eu/neochat.po
487
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/fi/neochat.po
486
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
494
po/fr/neochat.po
494
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
503
po/hu/neochat.po
503
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/ia/neochat.po
486
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/id/neochat.po
486
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
483
po/ie/neochat.po
483
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/it/neochat.po
486
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
477
po/ja/neochat.po
477
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/ka/neochat.po
486
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/ko/neochat.po
486
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
477
po/lt/neochat.po
477
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/nl/neochat.po
486
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/nn/neochat.po
488
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
480
po/pa/neochat.po
480
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/pl/neochat.po
486
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/pt/neochat.po
486
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
519
po/ru/neochat.po
519
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/sk/neochat.po
486
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/sl/neochat.po
486
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
486
po/sv/neochat.po
486
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
488
po/ta/neochat.po
488
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
496
po/tr/neochat.po
496
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
487
po/uk/neochat.po
487
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1181
po/zh_CN/neochat.po
1181
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -143,6 +143,7 @@ add_library(neochat STATIC
|
|||||||
models/notificationsmodel.h
|
models/notificationsmodel.h
|
||||||
models/timelinemodel.cpp
|
models/timelinemodel.cpp
|
||||||
models/timelinemodel.h
|
models/timelinemodel.h
|
||||||
|
enums/pushrule.h
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||||
|
|||||||
@@ -7,10 +7,7 @@
|
|||||||
#include <qt6keychain/keychain.h>
|
#include <qt6keychain/keychain.h>
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <KWindowConfig>
|
|
||||||
|
|
||||||
#include <QFile>
|
|
||||||
#include <QFileInfo>
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
#include <QQuickTextDocument>
|
#include <QQuickTextDocument>
|
||||||
@@ -28,13 +25,11 @@
|
|||||||
#include <Quotient/eventstats.h>
|
#include <Quotient/eventstats.h>
|
||||||
#include <Quotient/jobs/downloadfilejob.h>
|
#include <Quotient/jobs/downloadfilejob.h>
|
||||||
#include <Quotient/qt_connection_util.h>
|
#include <Quotient/qt_connection_util.h>
|
||||||
#include <Quotient/user.h>
|
|
||||||
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "windowcontroller.h"
|
|
||||||
|
|
||||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||||
#include "trayicon.h"
|
#include "trayicon.h"
|
||||||
@@ -68,7 +63,6 @@ Controller::Controller(QObject *parent)
|
|||||||
connect(c, &Connection::connected, this, [c, this]() {
|
connect(c, &Connection::connected, this, [c, this]() {
|
||||||
m_accountRegistry.add(c);
|
m_accountRegistry.add(c);
|
||||||
c->syncLoop();
|
c->syncLoop();
|
||||||
Q_EMIT initiated();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,8 +72,7 @@ Controller::Controller(QObject *parent)
|
|||||||
});
|
});
|
||||||
|
|
||||||
#ifndef Q_OS_WINDOWS
|
#ifndef Q_OS_WINDOWS
|
||||||
// Setup Unix signal handlers
|
const auto unixExitHandler = [](int) -> void {
|
||||||
const auto unixExitHandler = [](int /*sig*/) -> void {
|
|
||||||
QCoreApplication::quit();
|
QCoreApplication::quit();
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -105,7 +98,7 @@ Controller::Controller(QObject *parent)
|
|||||||
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
|
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
|
||||||
if (m_accountRegistry.size() > oldAccountCount) {
|
if (m_accountRegistry.size() > oldAccountCount) {
|
||||||
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
|
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
|
||||||
connect(connection, &NeoChatConnection::syncDone, this, [this, connection]() {
|
connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
|
||||||
NotificationsManager::instance().handleNotifications(connection);
|
NotificationsManager::instance().handleNotifications(connection);
|
||||||
});
|
});
|
||||||
connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
|
connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
|
||||||
@@ -137,23 +130,6 @@ Controller &Controller::instance()
|
|||||||
return _instance;
|
return _instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::toggleWindow()
|
|
||||||
{
|
|
||||||
auto &instance = WindowController::instance();
|
|
||||||
auto window = instance.window();
|
|
||||||
if (window->isVisible()) {
|
|
||||||
if (window->windowStates() & Qt::WindowMinimized) {
|
|
||||||
window->showNormal();
|
|
||||||
window->requestActivate();
|
|
||||||
} else {
|
|
||||||
window->close();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
instance.showAndRaiseWindow({});
|
|
||||||
instance.window()->requestActivate();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::addConnection(NeoChatConnection *c)
|
void Controller::addConnection(NeoChatConnection *c)
|
||||||
{
|
{
|
||||||
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
|
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
|
||||||
@@ -162,9 +138,7 @@ void Controller::addConnection(NeoChatConnection *c)
|
|||||||
|
|
||||||
c->setLazyLoading(true);
|
c->setLazyLoading(true);
|
||||||
|
|
||||||
connect(c, &NeoChatConnection::syncDone, this, [this, c] {
|
connect(c, &NeoChatConnection::syncDone, this, [c] {
|
||||||
Q_EMIT syncDone();
|
|
||||||
|
|
||||||
c->sync(30000);
|
c->sync(30000);
|
||||||
c->saveState();
|
c->saveState();
|
||||||
});
|
});
|
||||||
@@ -172,12 +146,6 @@ void Controller::addConnection(NeoChatConnection *c)
|
|||||||
dropConnection(c);
|
dropConnection(c);
|
||||||
});
|
});
|
||||||
|
|
||||||
connect(c, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
|
|
||||||
if (job->error() == BaseJob::UserConsentRequired) {
|
|
||||||
Q_EMIT userConsentRequired(job->errorUrl());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
c->sync();
|
c->sync();
|
||||||
|
|
||||||
Q_EMIT connectionAdded(c);
|
Q_EMIT connectionAdded(c);
|
||||||
@@ -194,18 +162,13 @@ void Controller::dropConnection(NeoChatConnection *c)
|
|||||||
void Controller::invokeLogin()
|
void Controller::invokeLogin()
|
||||||
{
|
{
|
||||||
const auto accounts = SettingsGroup("Accounts"_ls).childGroups();
|
const auto accounts = SettingsGroup("Accounts"_ls).childGroups();
|
||||||
QString id = NeoChatConfig::self()->activeConnection();
|
|
||||||
for (const auto &accountId : accounts) {
|
for (const auto &accountId : accounts) {
|
||||||
AccountSettings account{accountId};
|
AccountSettings account{accountId};
|
||||||
m_accountsLoading += accountId;
|
m_accountsLoading += accountId;
|
||||||
Q_EMIT accountsLoadingChanged();
|
Q_EMIT accountsLoadingChanged();
|
||||||
if (id.isEmpty()) {
|
|
||||||
// handle case where the account config is empty
|
|
||||||
id = accountId;
|
|
||||||
}
|
|
||||||
if (!account.homeserver().isEmpty()) {
|
if (!account.homeserver().isEmpty()) {
|
||||||
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
|
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
|
||||||
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
|
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
|
||||||
AccountSettings account{accountId};
|
AccountSettings account{accountId};
|
||||||
QString accessToken;
|
QString accessToken;
|
||||||
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
|
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
|
||||||
@@ -215,30 +178,11 @@ void Controller::invokeLogin()
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto connection = new NeoChatConnection(account.homeserver());
|
auto connection = new NeoChatConnection(account.homeserver());
|
||||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
|
connect(connection, &NeoChatConnection::connected, this, [this, connection] {
|
||||||
connection->loadState();
|
connection->loadState();
|
||||||
addConnection(connection);
|
addConnection(connection);
|
||||||
m_accountsLoading.removeAll(connection->userId());
|
m_accountsLoading.removeAll(connection->userId());
|
||||||
Q_EMIT accountsLoadingChanged();
|
Q_EMIT accountsLoadingChanged();
|
||||||
if (connection->userId() == id) {
|
|
||||||
setActiveConnection(connection);
|
|
||||||
connectSingleShot(connection, &NeoChatConnection::syncDone, this, &Controller::initiated);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
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"), {});
|
|
||||||
connection->logout(false);
|
|
||||||
} else if (error == "Connection closed"_ls) {
|
|
||||||
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), {});
|
|
||||||
connection->logout(true);
|
|
||||||
}
|
|
||||||
Q_EMIT initiated();
|
|
||||||
});
|
});
|
||||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
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), {});
|
||||||
@@ -247,9 +191,6 @@ void Controller::invokeLogin()
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (accounts.isEmpty()) {
|
|
||||||
Q_EMIT initiated();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
|
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
|
||||||
@@ -322,10 +263,8 @@ void Controller::setQuitOnLastWindowClosed()
|
|||||||
if (NeoChatConfig::self()->systemTray()) {
|
if (NeoChatConfig::self()->systemTray()) {
|
||||||
m_trayIcon = new TrayIcon(this);
|
m_trayIcon = new TrayIcon(this);
|
||||||
m_trayIcon->show();
|
m_trayIcon->show();
|
||||||
connect(m_trayIcon, &TrayIcon::toggleWindow, this, &Controller::toggleWindow);
|
|
||||||
} else {
|
} else {
|
||||||
if (m_trayIcon) {
|
if (m_trayIcon) {
|
||||||
disconnect(m_trayIcon, &TrayIcon::toggleWindow, this, &Controller::toggleWindow);
|
|
||||||
delete m_trayIcon;
|
delete m_trayIcon;
|
||||||
m_trayIcon = nullptr;
|
m_trayIcon = nullptr;
|
||||||
}
|
}
|
||||||
@@ -352,24 +291,16 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
|
|||||||
}
|
}
|
||||||
m_connection = connection;
|
m_connection = connection;
|
||||||
if (connection != nullptr) {
|
if (connection != nullptr) {
|
||||||
NeoChatConfig::self()->setActiveConnection(connection->userId());
|
|
||||||
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
|
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
|
||||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
|
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."));
|
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
NeoChatConfig::self()->setActiveConnection(QString());
|
|
||||||
}
|
}
|
||||||
NeoChatConfig::self()->save();
|
NeoChatConfig::self()->save();
|
||||||
Q_EMIT activeConnectionChanged();
|
Q_EMIT activeConnectionChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Controller::saveWindowGeometry()
|
|
||||||
{
|
|
||||||
WindowController::instance().saveGeometry();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
|
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
|
||||||
{
|
{
|
||||||
// HACK: Workaround bug QTBUG 93281
|
// HACK: Workaround bug QTBUG 93281
|
||||||
@@ -381,10 +312,18 @@ void Controller::listenForNotifications()
|
|||||||
#ifdef HAVE_KUNIFIEDPUSH
|
#ifdef HAVE_KUNIFIEDPUSH
|
||||||
auto connector = new KUnifiedPush::Connector(QStringLiteral("org.kde.neochat"));
|
auto connector = new KUnifiedPush::Connector(QStringLiteral("org.kde.neochat"));
|
||||||
|
|
||||||
connect(connector, &KUnifiedPush::Connector::messageReceived, [](const QByteArray &data) {
|
auto timer = new QTimer();
|
||||||
|
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
|
||||||
|
|
||||||
|
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
|
||||||
NotificationsManager::instance().postPushNotification(data);
|
NotificationsManager::instance().postPushNotification(data);
|
||||||
|
timer->stop();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
|
||||||
|
// Otherwise, messageReceived is never activated, and this daemon could stick around forever.
|
||||||
|
timer->start(5000);
|
||||||
|
|
||||||
connector->registerClient(i18n("Receiving push notifications"));
|
connector->registerClient(i18n("Receiving push notifications"));
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,23 +9,15 @@
|
|||||||
|
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
#include <Quotient/accountregistry.h>
|
#include <Quotient/accountregistry.h>
|
||||||
#include <Quotient/jobs/basejob.h>
|
|
||||||
#include <Quotient/settings.h>
|
#include <Quotient/settings.h>
|
||||||
|
|
||||||
#ifdef HAVE_KUNIFIEDPUSH
|
#ifdef HAVE_KUNIFIEDPUSH
|
||||||
#include <kunifiedpush/connector.h>
|
#include <kunifiedpush/connector.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
class NeoChatRoom;
|
|
||||||
class TrayIcon;
|
class TrayIcon;
|
||||||
class QQuickTextDocument;
|
class QQuickTextDocument;
|
||||||
|
|
||||||
namespace Quotient
|
|
||||||
{
|
|
||||||
class Room;
|
|
||||||
class User;
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace QKeychain
|
namespace QKeychain
|
||||||
{
|
{
|
||||||
class ReadPasswordJob;
|
class ReadPasswordJob;
|
||||||
@@ -128,7 +120,6 @@ private:
|
|||||||
|
|
||||||
void loadSettings();
|
void loadSettings();
|
||||||
void saveSettings() const;
|
void saveSettings() const;
|
||||||
QMap<Quotient::Room *, int> m_notificationCounts;
|
|
||||||
|
|
||||||
Quotient::AccountRegistry m_accountRegistry;
|
Quotient::AccountRegistry m_accountRegistry;
|
||||||
QStringList m_accountsLoading;
|
QStringList m_accountsLoading;
|
||||||
@@ -136,21 +127,12 @@ private:
|
|||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void invokeLogin();
|
void invokeLogin();
|
||||||
void toggleWindow();
|
|
||||||
void setQuitOnLastWindowClosed();
|
void setQuitOnLastWindowClosed();
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void errorOccured(const QString &error, const QString &detail);
|
void errorOccured(const QString &error, const QString &detail);
|
||||||
void syncDone();
|
|
||||||
void connectionAdded(NeoChatConnection *connection);
|
void connectionAdded(NeoChatConnection *connection);
|
||||||
void connectionDropped(NeoChatConnection *connection);
|
void connectionDropped(NeoChatConnection *connection);
|
||||||
void initiated();
|
|
||||||
void quitOnLastWindowClosedChanged();
|
|
||||||
void unreadCountChanged();
|
|
||||||
void activeConnectionChanged();
|
void activeConnectionChanged();
|
||||||
void userConsentRequired(QUrl url);
|
|
||||||
void accountsLoadingChanged();
|
void accountsLoadingChanged();
|
||||||
|
|
||||||
public Q_SLOTS:
|
|
||||||
void saveWindowGeometry();
|
|
||||||
};
|
};
|
||||||
|
|||||||
186
src/enums/pushrule.h
Normal file
186
src/enums/pushrule.h
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QObject>
|
||||||
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PushRuleKind
|
||||||
|
*
|
||||||
|
* A class with the Kind enum for push notifications and helper functions.
|
||||||
|
*
|
||||||
|
* The kind relates to the kinds of push rule defined in the matrix spec, see
|
||||||
|
* https://spec.matrix.org/v1.7/client-server-api/#push-rules for full details.
|
||||||
|
*/
|
||||||
|
class PushRuleKind : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the different kinds of push rule.
|
||||||
|
*/
|
||||||
|
enum Kind {
|
||||||
|
Override = 0, /**< The highest priority rules. */
|
||||||
|
Content, /**< These configure behaviour for messages that match certain patterns. */
|
||||||
|
Room, /**< These rules change the behaviour of all messages for a given room. */
|
||||||
|
Sender, /**< These rules configure notification behaviour for messages from a specific Matrix user ID. */
|
||||||
|
Underride, /**< These are identical to override rules, but have a lower priority than content, room and sender rules. */
|
||||||
|
};
|
||||||
|
Q_ENUM(Kind)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Translate the Kind enum value to a human readable string.
|
||||||
|
*
|
||||||
|
* @sa Kind
|
||||||
|
*/
|
||||||
|
static QString kindString(Kind kind)
|
||||||
|
{
|
||||||
|
switch (kind) {
|
||||||
|
case Kind::Override:
|
||||||
|
return QLatin1String("override");
|
||||||
|
case Kind::Content:
|
||||||
|
return QLatin1String("content");
|
||||||
|
case Kind::Room:
|
||||||
|
return QLatin1String("room");
|
||||||
|
case Kind::Sender:
|
||||||
|
return QLatin1String("sender");
|
||||||
|
case Kind::Underride:
|
||||||
|
return QLatin1String("underride");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PushRuleAction
|
||||||
|
*
|
||||||
|
* A class with the Action enum for push notifications.
|
||||||
|
*
|
||||||
|
* The action relates to the actions of push rule defined in the matrix spec, see
|
||||||
|
* https://spec.matrix.org/v1.7/client-server-api/#push-rules for full details.
|
||||||
|
*/
|
||||||
|
class PushRuleAction : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the global push notification actions.
|
||||||
|
*/
|
||||||
|
enum Action {
|
||||||
|
Unknown = 0, /**< The action has not yet been obtained from the server. */
|
||||||
|
Off, /**< No push notifications are to be sent. */
|
||||||
|
On, /**< Push notifications are on. */
|
||||||
|
Noisy, /**< Push notifications are on, also trigger a notification sound. */
|
||||||
|
Highlight, /**< Push notifications are on, also the event should be highlighted in chat. */
|
||||||
|
NoisyHighlight, /**< Push notifications are on, also trigger a notification sound and highlight in chat. */
|
||||||
|
};
|
||||||
|
Q_ENUM(Action)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PushNotificationState
|
||||||
|
*
|
||||||
|
* A class with the State enum for room push notification state.
|
||||||
|
*
|
||||||
|
* The state define whether the room adheres to the global push rule states for the
|
||||||
|
* account or is overridden for a room.
|
||||||
|
*
|
||||||
|
* @note This is different to the PushRuleAction which defines the type of notification
|
||||||
|
* for an individual rule.
|
||||||
|
*
|
||||||
|
* @sa PushRuleAction
|
||||||
|
*/
|
||||||
|
class PushNotificationState : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Describes the push notification state for the room.
|
||||||
|
*/
|
||||||
|
enum State {
|
||||||
|
Unknown, /**< The state has not yet been obtained from the server. */
|
||||||
|
Default, /**< The room follows the globally configured rules for the local user. */
|
||||||
|
Mute, /**< No notifications for messages in the room. */
|
||||||
|
MentionKeyword, /**< Notifications only for local user mentions and keywords. */
|
||||||
|
All, /**< Notifications for all messages. */
|
||||||
|
};
|
||||||
|
Q_ENUM(State)
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class PushRuleSection
|
||||||
|
*
|
||||||
|
* A class with the Section enum for push notifications and helper functions.
|
||||||
|
*
|
||||||
|
* @note This is different from the PushRuleKind and instead is used for sorting
|
||||||
|
* in the settings page which is not necessarily by Kind.
|
||||||
|
*
|
||||||
|
* @sa PushRuleKind
|
||||||
|
*/
|
||||||
|
class PushRuleSection : public QObject
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the sections to sort push rules into.
|
||||||
|
*/
|
||||||
|
enum Section {
|
||||||
|
Master = 0, /**< The master push rule */
|
||||||
|
Room, /**< Push rules relating to all rooms. */
|
||||||
|
Mentions, /**< Push rules relating to user mentions. */
|
||||||
|
Keywords, /**< Global Keyword push rules. */
|
||||||
|
RoomKeywords, /**< Keyword push rules that only apply to a specific room. */
|
||||||
|
Invites, /**< Push rules relating to invites. */
|
||||||
|
Unknown, /**< New default push rules that have not been added to the model yet. */
|
||||||
|
/**
|
||||||
|
* @brief Push rules that should never be shown.
|
||||||
|
*
|
||||||
|
* There are numerous rules that get set that shouldn't be shown in the general
|
||||||
|
* list e.g. The array of rules used to override global settings in individual
|
||||||
|
* rooms.
|
||||||
|
*
|
||||||
|
* This is specifically different to unknown which are just new default push
|
||||||
|
* rule that haven't been added to the model yet.
|
||||||
|
*/
|
||||||
|
Undefined,
|
||||||
|
};
|
||||||
|
Q_ENUM(Section)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Translate the Section enum value to a human readable string.
|
||||||
|
*
|
||||||
|
* @sa Section
|
||||||
|
*/
|
||||||
|
static QString sectionString(Section section)
|
||||||
|
{
|
||||||
|
switch (section) {
|
||||||
|
case Section::Master:
|
||||||
|
return QLatin1String("Master");
|
||||||
|
case Section::Room:
|
||||||
|
return QLatin1String("Room Notifications");
|
||||||
|
case Section::Mentions:
|
||||||
|
return QLatin1String("@Mentions");
|
||||||
|
case Section::Keywords:
|
||||||
|
return QLatin1String("Keywords");
|
||||||
|
case Section::Invites:
|
||||||
|
return QLatin1String("Invites");
|
||||||
|
default:
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -355,7 +355,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
|||||||
if (e.repeatsState()) {
|
if (e.repeatsState()) {
|
||||||
auto text = i18n("reinvited %1 to the room", subjectName);
|
auto text = i18n("reinvited %1 to the room", subjectName);
|
||||||
if (!e.reason().isEmpty()) {
|
if (!e.reason().isEmpty()) {
|
||||||
text += i18nc("Optional reason for an invitation", ": %1") + e.reason().toHtmlEscaped();
|
text += i18nc("Optional reason for an invitation", ": %1") + (prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
|
||||||
}
|
}
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
@@ -379,7 +379,9 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
|||||||
if (!e.newDisplayName()) {
|
if (!e.newDisplayName()) {
|
||||||
text = i18nc("their refers to a singular user", "cleared their display name");
|
text = i18nc("their refers to a singular user", "cleared their display name");
|
||||||
} else {
|
} else {
|
||||||
text = i18nc("their refers to a singular user", "changed their display name to %1", e.newDisplayName()->toHtmlEscaped());
|
text = i18nc("their refers to a singular user",
|
||||||
|
"changed their display name to %1",
|
||||||
|
prettyPrint ? e.newDisplayName()->toHtmlEscaped() : *e.newDisplayName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (e.isAvatarUpdate()) {
|
if (e.isAvatarUpdate()) {
|
||||||
@@ -415,7 +417,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
|||||||
if (e.reason().isEmpty()) {
|
if (e.reason().isEmpty()) {
|
||||||
return i18n("banned %1 from the room", subjectName);
|
return i18n("banned %1 from the room", subjectName);
|
||||||
} else {
|
} else {
|
||||||
return i18n("banned %1 from the room: %2", subjectName, e.reason().toHtmlEscaped());
|
return i18n("banned %1 from the room: %2", subjectName, prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return i18n("self-banned from the room");
|
return i18n("self-banned from the room");
|
||||||
@@ -431,8 +433,8 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
|||||||
[](const RoomCanonicalAliasEvent &e) {
|
[](const RoomCanonicalAliasEvent &e) {
|
||||||
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias to: %1", e.alias());
|
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias to: %1", e.alias());
|
||||||
},
|
},
|
||||||
[](const RoomNameEvent &e) {
|
[prettyPrint](const RoomNameEvent &e) {
|
||||||
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
|
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", prettyPrint ? e.name().toHtmlEscaped() : e.name());
|
||||||
},
|
},
|
||||||
[prettyPrint, stripNewlines](const RoomTopicEvent &e) {
|
[prettyPrint, stripNewlines](const RoomTopicEvent &e) {
|
||||||
return (e.topic().isEmpty()) ? i18n("cleared the topic")
|
return (e.topic().isEmpty()) ? i18n("cleared the topic")
|
||||||
@@ -447,14 +449,15 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
|||||||
[](const EncryptionEvent &) {
|
[](const EncryptionEvent &) {
|
||||||
return i18n("activated End-to-End Encryption");
|
return i18n("activated End-to-End Encryption");
|
||||||
},
|
},
|
||||||
[](const RoomCreateEvent &e) {
|
[prettyPrint](const RoomCreateEvent &e) {
|
||||||
return e.isUpgrade() ? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped())
|
return e.isUpgrade()
|
||||||
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped());
|
? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()))
|
||||||
|
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()));
|
||||||
},
|
},
|
||||||
[](const RoomPowerLevelsEvent &) {
|
[](const RoomPowerLevelsEvent &) {
|
||||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||||
},
|
},
|
||||||
[](const StateEvent &e) {
|
[prettyPrint](const StateEvent &e) {
|
||||||
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
|
||||||
return i18n("changed the server access control lists for this room");
|
return i18n("changed the server access control lists for this room");
|
||||||
}
|
}
|
||||||
@@ -471,7 +474,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
|||||||
return e.contentJson()["description"_ls].toString();
|
return e.contentJson()["description"_ls].toString();
|
||||||
}
|
}
|
||||||
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
|
||||||
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
|
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
|
||||||
},
|
},
|
||||||
[](const PollStartEvent &e) {
|
[](const PollStartEvent &e) {
|
||||||
return e.question();
|
return e.question();
|
||||||
@@ -774,97 +777,6 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
|
|||||||
return mediaInfo;
|
return mediaInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString text;
|
|
||||||
auto event = eventCast<const RoomMessageEvent>(m_event);
|
|
||||||
if (event->hasTextContent()) {
|
|
||||||
auto textContent = static_cast<const EventContent::TextContent *>(event->content());
|
|
||||||
if (textContent) {
|
|
||||||
text = textContent->body;
|
|
||||||
} else {
|
|
||||||
text = event->plainBody();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
text = event->plainBody();
|
|
||||||
}
|
|
||||||
TextHandler textHandler;
|
|
||||||
textHandler.setData(text);
|
|
||||||
|
|
||||||
QList<QUrl> links = textHandler.getLinkPreviews();
|
|
||||||
if (links.size() > 0) {
|
|
||||||
return QSharedPointer<LinkPreviewer>(new LinkPreviewer(nullptr, m_room, links.size() > 0 ? links[0] : QUrl()));
|
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QSharedPointer<ReactionModel> EventHandler::getReactions() const
|
|
||||||
{
|
|
||||||
if (m_room == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getReactions called with m_room set to nullptr.";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (m_event == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getReactions called with m_event set to nullptr.";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
if (!m_event->is<RoomMessageEvent>()) {
|
|
||||||
qCWarning(EventHandling) << "getReactions called with on a non-message event.";
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto eventId = m_event->id();
|
|
||||||
const auto &annotations = m_room->relatedEvents(eventId, EventRelation::AnnotationType);
|
|
||||||
if (annotations.isEmpty()) {
|
|
||||||
return nullptr;
|
|
||||||
};
|
|
||||||
|
|
||||||
QMap<QString, QList<User *>> reactions = {};
|
|
||||||
for (const auto &a : annotations) {
|
|
||||||
if (a->isRedacted()) { // Just in case?
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (const auto &e = eventCast<const ReactionEvent>(a)) {
|
|
||||||
reactions[e->key()].append(m_room->user(e->senderId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (reactions.isEmpty()) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
QList<ReactionModel::Reaction> res;
|
|
||||||
auto i = reactions.constBegin();
|
|
||||||
while (i != reactions.constEnd()) {
|
|
||||||
QVariantList authors;
|
|
||||||
for (const auto &author : i.value()) {
|
|
||||||
authors.append(m_room->getUser(author));
|
|
||||||
}
|
|
||||||
|
|
||||||
res.append(ReactionModel::Reaction{i.key(), authors});
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (res.size() > 0) {
|
|
||||||
return QSharedPointer<ReactionModel>(new ReactionModel(nullptr, res, m_room->localUser()));
|
|
||||||
} else {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool EventHandler::hasReply() const
|
bool EventHandler::hasReply() const
|
||||||
{
|
{
|
||||||
if (m_event == nullptr) {
|
if (m_event == nullptr) {
|
||||||
|
|||||||
@@ -231,25 +231,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
QVariantMap getMediaInfo() const;
|
QVariantMap getMediaInfo() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Return a LinkPreviewer object for the event.
|
|
||||||
*
|
|
||||||
* A nullptr will be returned for any event that doesn't have any links so the
|
|
||||||
* return should be null checked and an empty LinkPreviewer provided if null.
|
|
||||||
*
|
|
||||||
* @sa LinkPreviewer
|
|
||||||
*/
|
|
||||||
QSharedPointer<LinkPreviewer> getLinkPreviewer() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Return a ReactionModel object for the event.
|
|
||||||
*
|
|
||||||
* A nullptr will be returned for any event that doesn't have any links so the
|
|
||||||
* return should be null checked and an empty QVariantList (or other suitable
|
|
||||||
* empty mode) provided if null.
|
|
||||||
*/
|
|
||||||
QSharedPointer<ReactionModel> getReactions() const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the event is a reply to another in the timeline.
|
* @brief Whether the event is a reply to another in the timeline.
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -3,25 +3,37 @@
|
|||||||
|
|
||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
|
|
||||||
#include "controller.h"
|
|
||||||
|
|
||||||
#include <Quotient/connection.h>
|
#include <Quotient/connection.h>
|
||||||
#include <Quotient/csapi/content-repo.h>
|
#include <Quotient/csapi/content-repo.h>
|
||||||
|
#include <Quotient/events/roommessageevent.h>
|
||||||
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
LinkPreviewer::LinkPreviewer(QObject *parent, const NeoChatRoom *room, const QUrl &url)
|
LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event)
|
||||||
: QObject(parent)
|
: QObject(nullptr)
|
||||||
, m_currentRoom(room)
|
, m_currentRoom(room)
|
||||||
|
, m_event(event)
|
||||||
, m_loaded(false)
|
, m_loaded(false)
|
||||||
, m_url(url)
|
, m_url(linkPreview(event))
|
||||||
{
|
{
|
||||||
loadUrlPreview();
|
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
|
||||||
if (m_currentRoom) {
|
|
||||||
|
if (m_event != nullptr && m_currentRoom != nullptr) {
|
||||||
|
loadUrlPreview();
|
||||||
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
|
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
|
||||||
|
// Make sure that we react to edits
|
||||||
|
connect(m_currentRoom, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
|
||||||
|
if (m_event->id() == newEvent->id()) {
|
||||||
|
m_event = eventCast<const Quotient::RoomMessageEvent>(newEvent);
|
||||||
|
m_url = linkPreview(m_event);
|
||||||
|
Q_EMIT urlChanged();
|
||||||
|
loadUrlPreview();
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
|
||||||
}
|
}
|
||||||
@@ -51,15 +63,6 @@ QUrl LinkPreviewer::url() const
|
|||||||
return m_url;
|
return m_url;
|
||||||
}
|
}
|
||||||
|
|
||||||
void LinkPreviewer::setUrl(QUrl url)
|
|
||||||
{
|
|
||||||
if (url != m_url) {
|
|
||||||
m_url = url;
|
|
||||||
urlChanged();
|
|
||||||
loadUrlPreview();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void LinkPreviewer::loadUrlPreview()
|
void LinkPreviewer::loadUrlPreview()
|
||||||
{
|
{
|
||||||
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
|
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
|
||||||
@@ -98,4 +101,38 @@ bool LinkPreviewer::empty() const
|
|||||||
return m_url.isEmpty();
|
return m_url.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
|
||||||
|
{
|
||||||
|
if (event == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString text;
|
||||||
|
if (event->hasTextContent()) {
|
||||||
|
auto textContent = static_cast<const Quotient::EventContent::TextContent *>(event->content());
|
||||||
|
if (textContent) {
|
||||||
|
text = textContent->body;
|
||||||
|
} else {
|
||||||
|
text = event->plainBody();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
text = event->plainBody();
|
||||||
|
}
|
||||||
|
|
||||||
|
auto data = text.remove(TextRegex::removeRichReply);
|
||||||
|
auto linksMatch = TextRegex::url.globalMatch(data);
|
||||||
|
while (linksMatch.hasNext()) {
|
||||||
|
auto link = linksMatch.next().captured();
|
||||||
|
if (!link.contains(QStringLiteral("matrix.to"))) {
|
||||||
|
return QUrl(link);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
|
||||||
|
{
|
||||||
|
return !linkPreview(event).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_linkpreviewer.cpp"
|
#include "moc_linkpreviewer.cpp"
|
||||||
|
|||||||
@@ -7,6 +7,11 @@
|
|||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QUrl>
|
#include <QUrl>
|
||||||
|
|
||||||
|
namespace Quotient
|
||||||
|
{
|
||||||
|
class RoomMessageEvent;
|
||||||
|
}
|
||||||
|
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -25,7 +30,7 @@ class LinkPreviewer : public QObject
|
|||||||
/**
|
/**
|
||||||
* @brief The URL to get the preview for.
|
* @brief The URL to get the preview for.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
|
Q_PROPERTY(QUrl url READ url NOTIFY urlChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the preview information has been loaded.
|
* @brief Whether the preview information has been loaded.
|
||||||
@@ -55,18 +60,25 @@ class LinkPreviewer : public QObject
|
|||||||
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
|
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
explicit LinkPreviewer(QObject *parent = nullptr, const NeoChatRoom *room = nullptr, const QUrl &url = {});
|
explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] QUrl url() const;
|
[[nodiscard]] QUrl url() const;
|
||||||
void setUrl(QUrl);
|
|
||||||
[[nodiscard]] bool loaded() const;
|
[[nodiscard]] bool loaded() const;
|
||||||
[[nodiscard]] QString title() const;
|
[[nodiscard]] QString title() const;
|
||||||
[[nodiscard]] QString description() const;
|
[[nodiscard]] QString description() const;
|
||||||
[[nodiscard]] QUrl imageSource() const;
|
[[nodiscard]] QUrl imageSource() const;
|
||||||
[[nodiscard]] bool empty() const;
|
[[nodiscard]] bool empty() const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the given event has at least 1 pre-viewable link.
|
||||||
|
*
|
||||||
|
* A link is only pre-viewable if it is http, https or something starting with www.
|
||||||
|
*/
|
||||||
|
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const NeoChatRoom *m_currentRoom = nullptr;
|
const NeoChatRoom *m_currentRoom;
|
||||||
|
const Quotient::RoomMessageEvent *m_event;
|
||||||
|
|
||||||
bool m_loaded;
|
bool m_loaded;
|
||||||
QString m_title = QString();
|
QString m_title = QString();
|
||||||
@@ -76,6 +88,14 @@ private:
|
|||||||
|
|
||||||
void loadUrlPreview();
|
void loadUrlPreview();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Return the link to be previewed from the given event.
|
||||||
|
*
|
||||||
|
* This function is designed to give only links that should be previewed so
|
||||||
|
* http, https or something starting with www. The first valid link is returned.
|
||||||
|
*/
|
||||||
|
static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void loadedChanged();
|
void loadedChanged();
|
||||||
void titleChanged();
|
void titleChanged();
|
||||||
|
|||||||
@@ -102,7 +102,6 @@ void LoginHelper::init()
|
|||||||
|
|
||||||
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
|
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
|
||||||
Q_EMIT loaded();
|
Q_EMIT loaded();
|
||||||
Q_EMIT Controller::instance().initiated();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -12,7 +12,6 @@
|
|||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
#include "controller.h"
|
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
|
|
||||||
#include <Quotient/connection.h>
|
#include <Quotient/connection.h>
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
#include "actionsmodel.h"
|
#include "actionsmodel.h"
|
||||||
|
|
||||||
#include "chatbarcache.h"
|
#include "chatbarcache.h"
|
||||||
#include "controller.h"
|
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include <Quotient/events/roommemberevent.h>
|
#include <Quotient/events/roommemberevent.h>
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
#include <QImage>
|
#include <QImage>
|
||||||
#include <QMimeDatabase>
|
#include <QMimeDatabase>
|
||||||
|
|
||||||
#include "controller.h"
|
|
||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
|
|
||||||
#include <Quotient/connection.h>
|
|
||||||
#include <Quotient/csapi/account-data.h>
|
#include <Quotient/csapi/account-data.h>
|
||||||
#include <Quotient/csapi/content-repo.h>
|
#include <Quotient/csapi/content-repo.h>
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,8 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
#include <QRegularExpression>
|
#include <QRegularExpression>
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class NeoChatConnection;
|
#include "neochatconnection.h"
|
||||||
|
|
||||||
struct CustomEmoji {
|
struct CustomEmoji {
|
||||||
QString name; // with :semicolons:
|
QString name; // with :semicolons:
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
|
|
||||||
#include "devicesmodel.h"
|
#include "devicesmodel.h"
|
||||||
|
|
||||||
#include "controller.h"
|
|
||||||
#include "jobs/neochatdeletedevicejob.h"
|
#include "jobs/neochatdeletedevicejob.h"
|
||||||
|
|
||||||
#include <QDateTime>
|
#include <QDateTime>
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-3.0-only
|
||||||
|
|
||||||
#include "messageeventmodel.h"
|
#include "messageeventmodel.h"
|
||||||
|
#include "linkpreviewer.h"
|
||||||
#include "messageeventmodel_logging.h"
|
#include "messageeventmodel_logging.h"
|
||||||
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
@@ -20,7 +21,9 @@
|
|||||||
|
|
||||||
#include "enums/delegatetype.h"
|
#include "enums/delegatetype.h"
|
||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
|
#include "events/pollevent.h"
|
||||||
#include "models/reactionmodel.h"
|
#include "models/reactionmodel.h"
|
||||||
|
#include "texthandler.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
@@ -34,7 +37,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[TimeStringRole] = "timeString";
|
roles[TimeStringRole] = "timeString";
|
||||||
roles[SectionRole] = "section";
|
roles[SectionRole] = "section";
|
||||||
roles[AuthorRole] = "author";
|
roles[AuthorRole] = "author";
|
||||||
roles[ContentRole] = "content";
|
|
||||||
roles[HighlightRole] = "isHighlighted";
|
roles[HighlightRole] = "isHighlighted";
|
||||||
roles[SpecialMarksRole] = "marks";
|
roles[SpecialMarksRole] = "marks";
|
||||||
roles[ProgressInfoRole] = "progressInfo";
|
roles[ProgressInfoRole] = "progressInfo";
|
||||||
@@ -65,12 +67,19 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[LatitudeRole] = "latitude";
|
roles[LatitudeRole] = "latitude";
|
||||||
roles[LongitudeRole] = "longitude";
|
roles[LongitudeRole] = "longitude";
|
||||||
roles[AssetRole] = "asset";
|
roles[AssetRole] = "asset";
|
||||||
|
roles[PollHandlerRole] = "pollHandler";
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageEventModel::MessageEventModel(QObject *parent)
|
MessageEventModel::MessageEventModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
|
connect(this, &MessageEventModel::modelAboutToBeReset, this, [this]() {
|
||||||
|
resetting = true;
|
||||||
|
});
|
||||||
|
connect(this, &MessageEventModel::modelReset, this, [this]() {
|
||||||
|
resetting = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
NeoChatRoom *MessageEventModel::room() const
|
NeoChatRoom *MessageEventModel::room() const
|
||||||
@@ -101,6 +110,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
if (const auto &roomMessageEvent = &*event->viewAs<RoomMessageEvent>()) {
|
if (const auto &roomMessageEvent = &*event->viewAs<RoomMessageEvent>()) {
|
||||||
createEventObjects(roomMessageEvent);
|
createEventObjects(roomMessageEvent);
|
||||||
}
|
}
|
||||||
|
if (event->event()->is<PollStartEvent>()) {
|
||||||
|
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event->event()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
|
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
|
||||||
@@ -151,6 +163,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (event->is<PollStartEvent>()) {
|
||||||
|
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1);
|
beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1);
|
||||||
@@ -160,6 +175,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
if (const auto &roomMessageEvent = dynamic_cast<RoomMessageEvent *>(event.get())) {
|
if (const auto &roomMessageEvent = dynamic_cast<RoomMessageEvent *>(event.get())) {
|
||||||
createEventObjects(roomMessageEvent);
|
createEventObjects(roomMessageEvent);
|
||||||
}
|
}
|
||||||
|
if (event->is<PollStartEvent>()) {
|
||||||
|
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (rowCount() > 0) {
|
if (rowCount() > 0) {
|
||||||
rowBelowInserted = rowCount() - 1; // See #312
|
rowBelowInserted = rowCount() - 1; // See #312
|
||||||
@@ -221,6 +239,10 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
||||||
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
|
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
|
||||||
|
const RoomMessageEvent *message = eventCast<const RoomMessageEvent>(newEvent);
|
||||||
|
if (message != nullptr) {
|
||||||
|
createEventObjects(message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
|
connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
|
||||||
if (eventId.isEmpty()) { // How did we get here?
|
if (eventId.isEmpty()) { // How did we get here?
|
||||||
@@ -231,8 +253,11 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
if (const auto &event = dynamic_cast<const RoomMessageEvent *>(&**eventIt)) {
|
if (const auto &event = dynamic_cast<const RoomMessageEvent *>(&**eventIt)) {
|
||||||
createEventObjects(event);
|
createEventObjects(event);
|
||||||
}
|
}
|
||||||
|
if (eventIt->event()->is<PollStartEvent>()) {
|
||||||
|
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(eventIt->event()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole, Qt::DisplayRole});
|
refreshEventRoles(eventId, {Qt::DisplayRole});
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::changed, this, [this]() {
|
connect(m_currentRoom, &Room::changed, this, [this]() {
|
||||||
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||||
@@ -253,6 +278,12 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
lastReadEventId.clear();
|
lastReadEventId.clear();
|
||||||
}
|
}
|
||||||
endResetModel();
|
endResetModel();
|
||||||
|
|
||||||
|
// After reset put a read marker in if required.
|
||||||
|
// This is needed when changing back to a room that has already loaded messages.
|
||||||
|
if (room) {
|
||||||
|
moveReadMarker(m_currentRoom->lastFullyReadEventId());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::refreshEvent(const QString &eventId)
|
int MessageEventModel::refreshEvent(const QString &eventId)
|
||||||
@@ -479,28 +510,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return eventHandler.getAuthor(isPending);
|
return eventHandler.getAuthor(isPending);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == ContentRole) {
|
|
||||||
if (evt.isRedacted()) {
|
|
||||||
auto reason = evt.redactedBecause()->reason();
|
|
||||||
return (reason.isEmpty()) ? i18n("[REDACTED]") : i18n("[REDACTED: %1]").arg(evt.redactedBecause()->reason());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
|
||||||
if (e->msgtype() == Quotient::MessageEventType::Location) {
|
|
||||||
return e->contentJson();
|
|
||||||
}
|
|
||||||
// Cannot use e.contentJson() here because some
|
|
||||||
// EventContent classes inject values into the copy of the
|
|
||||||
// content JSON stored in EventContent::Base
|
|
||||||
return e->hasFileContent() ? QVariant::fromValue(e->content()->originalJson) : QVariant();
|
|
||||||
};
|
|
||||||
|
|
||||||
if (auto e = eventCast<const StickerEvent>(&evt)) {
|
|
||||||
return QVariant::fromValue(e->image().originalJson);
|
|
||||||
}
|
|
||||||
return evt.contentJson();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == HighlightRole) {
|
if (role == HighlightRole) {
|
||||||
return eventHandler.isHighlighted();
|
return eventHandler.isHighlighted();
|
||||||
}
|
}
|
||||||
@@ -696,6 +705,10 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return row < static_cast<int>(m_currentRoom->pendingEvents().size());
|
return row < static_cast<int>(m_currentRoom->pendingEvents().size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (role == PollHandlerRole) {
|
||||||
|
return QVariant::fromValue<PollHandler *>(m_currentRoom->poll(evt.id()));
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -713,19 +726,37 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
|
|||||||
{
|
{
|
||||||
auto eventId = event->id();
|
auto eventId = event->id();
|
||||||
|
|
||||||
EventHandler eventHandler;
|
if (m_linkPreviewers.contains(eventId)) {
|
||||||
eventHandler.setRoom(m_currentRoom);
|
if (!LinkPreviewer::hasPreviewableLinks(event)) {
|
||||||
eventHandler.setEvent(event);
|
m_linkPreviewers.remove(eventId);
|
||||||
|
}
|
||||||
if (auto linkPreviewer = eventHandler.getLinkPreviewer()) {
|
|
||||||
m_linkPreviewers[eventId] = linkPreviewer;
|
|
||||||
} else {
|
} else {
|
||||||
m_linkPreviewers.remove(eventId);
|
if (LinkPreviewer::hasPreviewableLinks(event)) {
|
||||||
|
m_linkPreviewers[eventId] = QSharedPointer<LinkPreviewer>(new LinkPreviewer(m_currentRoom, event));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (auto reactionModel = eventHandler.getReactions()) {
|
|
||||||
m_reactionModels[eventId] = reactionModel;
|
// ReactionModel handles updates to add and remove reactions, we only need to
|
||||||
|
// handle adding and removing whole models here.
|
||||||
|
if (m_reactionModels.contains(eventId)) {
|
||||||
|
// If a model already exists but now has no reactions remove it
|
||||||
|
if (m_reactionModels[eventId]->rowCount() <= 0) {
|
||||||
|
m_reactionModels.remove(eventId);
|
||||||
|
if (!resetting) {
|
||||||
|
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
m_reactionModels.remove(eventId);
|
if (m_currentRoom->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
|
||||||
|
// If a model doesn't exist and there are reactions add it.
|
||||||
|
auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(event, m_currentRoom));
|
||||||
|
if (reactionModel->rowCount() > 0) {
|
||||||
|
m_reactionModels[eventId] = reactionModel;
|
||||||
|
if (!resetting) {
|
||||||
|
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
|
|
||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
#include "pollhandler.h"
|
||||||
|
|
||||||
class ReactionModel;
|
class ReactionModel;
|
||||||
|
|
||||||
@@ -45,7 +46,6 @@ public:
|
|||||||
TimeStringRole, /**< The timestamp for when the event was sent as a string (in QLocale::ShortFormat). */
|
TimeStringRole, /**< The timestamp for when the event was sent as a string (in QLocale::ShortFormat). */
|
||||||
SectionRole, /**< The date of the event as a string. */
|
SectionRole, /**< The date of the event as a string. */
|
||||||
AuthorRole, /**< The author of the event. */
|
AuthorRole, /**< The author of the event. */
|
||||||
ContentRole, /**< The full message content. */
|
|
||||||
HighlightRole, /**< Whether the event should be highlighted. */
|
HighlightRole, /**< Whether the event should be highlighted. */
|
||||||
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
||||||
ProgressInfoRole, /**< Progress info when downloading files. */
|
ProgressInfoRole, /**< Progress info when downloading files. */
|
||||||
@@ -83,6 +83,7 @@ public:
|
|||||||
LatitudeRole, /**< Latitude for a location event. */
|
LatitudeRole, /**< Latitude for a location event. */
|
||||||
LongitudeRole, /**< Longitude for a location event. */
|
LongitudeRole, /**< Longitude for a location event. */
|
||||||
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||||
|
PollHandlerRole, /**< The PollHandler for the event, if any. */
|
||||||
LastRole, // Keep this last
|
LastRole, // Keep this last
|
||||||
};
|
};
|
||||||
Q_ENUM(EventRoles)
|
Q_ENUM(EventRoles)
|
||||||
@@ -130,6 +131,7 @@ private:
|
|||||||
QString lastReadEventId;
|
QString lastReadEventId;
|
||||||
QPersistentModelIndex m_lastReadEventIndex;
|
QPersistentModelIndex m_lastReadEventIndex;
|
||||||
int rowBelowInserted = -1;
|
int rowBelowInserted = -1;
|
||||||
|
bool resetting = false;
|
||||||
bool movingEvent = false;
|
bool movingEvent = false;
|
||||||
KFormat m_format;
|
KFormat m_format;
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
#include <Quotient/csapi/pushrules.h>
|
#include <Quotient/csapi/pushrules.h>
|
||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
|
|
||||||
#include "controller.h"
|
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
|
|
||||||
#include <KLazyLocalizedString>
|
#include <KLazyLocalizedString>
|
||||||
@@ -34,25 +33,25 @@ static const QHash<QString, KLazyLocalizedString> defaultRuleNames = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Sections for default rules.
|
// Sections for default rules.
|
||||||
static const QHash<QString, PushNotificationSection::Section> defaultSections = {
|
static const QHash<QString, PushRuleSection::Section> defaultSections = {
|
||||||
{QStringLiteral(".m.rule.master"), PushNotificationSection::Master},
|
{QStringLiteral(".m.rule.master"), PushRuleSection::Master},
|
||||||
{QStringLiteral(".m.rule.room_one_to_one"), PushNotificationSection::Room},
|
{QStringLiteral(".m.rule.room_one_to_one"), PushRuleSection::Room},
|
||||||
{QStringLiteral(".m.rule.encrypted_room_one_to_one"), PushNotificationSection::Room},
|
{QStringLiteral(".m.rule.encrypted_room_one_to_one"), PushRuleSection::Room},
|
||||||
{QStringLiteral(".m.rule.message"), PushNotificationSection::Room},
|
{QStringLiteral(".m.rule.message"), PushRuleSection::Room},
|
||||||
{QStringLiteral(".m.rule.encrypted"), PushNotificationSection::Room},
|
{QStringLiteral(".m.rule.encrypted"), PushRuleSection::Room},
|
||||||
{QStringLiteral(".m.rule.tombstone"), PushNotificationSection::Room},
|
{QStringLiteral(".m.rule.tombstone"), PushRuleSection::Room},
|
||||||
{QStringLiteral(".m.rule.contains_display_name"), PushNotificationSection::Mentions},
|
{QStringLiteral(".m.rule.contains_display_name"), PushRuleSection::Mentions},
|
||||||
{QStringLiteral(".m.rule.is_user_mention"), PushNotificationSection::Mentions},
|
{QStringLiteral(".m.rule.is_user_mention"), PushRuleSection::Mentions},
|
||||||
{QStringLiteral(".m.rule.is_room_mention"), PushNotificationSection::Mentions},
|
{QStringLiteral(".m.rule.is_room_mention"), PushRuleSection::Mentions},
|
||||||
{QStringLiteral(".m.rule.contains_user_name"), PushNotificationSection::Mentions},
|
{QStringLiteral(".m.rule.contains_user_name"), PushRuleSection::Mentions},
|
||||||
{QStringLiteral(".m.rule.roomnotif"), PushNotificationSection::Mentions},
|
{QStringLiteral(".m.rule.roomnotif"), PushRuleSection::Mentions},
|
||||||
{QStringLiteral(".m.rule.invite_for_me"), PushNotificationSection::Invites},
|
{QStringLiteral(".m.rule.invite_for_me"), PushRuleSection::Invites},
|
||||||
{QStringLiteral(".m.rule.call"), PushNotificationSection::Undefined}, // TODO: make invites when VOIP added.
|
{QStringLiteral(".m.rule.call"), PushRuleSection::Undefined}, // TODO: make invites when VOIP added.
|
||||||
{QStringLiteral(".m.rule.suppress_notices"), PushNotificationSection::Undefined},
|
{QStringLiteral(".m.rule.suppress_notices"), PushRuleSection::Undefined},
|
||||||
{QStringLiteral(".m.rule.member_event"), PushNotificationSection::Undefined},
|
{QStringLiteral(".m.rule.member_event"), PushRuleSection::Undefined},
|
||||||
{QStringLiteral(".m.rule.reaction"), PushNotificationSection::Undefined},
|
{QStringLiteral(".m.rule.reaction"), PushRuleSection::Undefined},
|
||||||
{QStringLiteral(".m.rule.room.server_acl"), PushNotificationSection::Undefined},
|
{QStringLiteral(".m.rule.room.server_acl"), PushRuleSection::Undefined},
|
||||||
{QStringLiteral(".im.vector.jitsi"), PushNotificationSection::Undefined},
|
{QStringLiteral(".im.vector.jitsi"), PushRuleSection::Undefined},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Default rules that don't have a highlight option as it would lead to all messages
|
// Default rules that don't have a highlight option as it would lead to all messages
|
||||||
@@ -67,7 +66,7 @@ static const QStringList noHighlight = {
|
|||||||
PushRuleModel::PushRuleModel(QObject *parent)
|
PushRuleModel::PushRuleModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
m_defaultKeywordAction = static_cast<PushNotificationAction::Action>(NeoChatConfig::self()->keywordPushRuleDefault());
|
m_defaultKeywordAction = static_cast<PushRuleAction::Action>(NeoChatConfig::self()->keywordPushRuleDefault());
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushRuleModel::updateNotificationRules(const QString &type)
|
void PushRuleModel::updateNotificationRules(const QString &type)
|
||||||
@@ -83,11 +82,11 @@ void PushRuleModel::updateNotificationRules(const QString &type)
|
|||||||
m_rules.clear();
|
m_rules.clear();
|
||||||
|
|
||||||
// Doing this 5 times because PushRuleset is a struct.
|
// Doing this 5 times because PushRuleset is a struct.
|
||||||
setRules(ruleData.override, PushNotificationKind::Override);
|
setRules(ruleData.override, PushRuleKind::Override);
|
||||||
setRules(ruleData.content, PushNotificationKind::Content);
|
setRules(ruleData.content, PushRuleKind::Content);
|
||||||
setRules(ruleData.room, PushNotificationKind::Room);
|
setRules(ruleData.room, PushRuleKind::Room);
|
||||||
setRules(ruleData.sender, PushNotificationKind::Sender);
|
setRules(ruleData.sender, PushRuleKind::Sender);
|
||||||
setRules(ruleData.underride, PushNotificationKind::Underride);
|
setRules(ruleData.underride, PushRuleKind::Underride);
|
||||||
|
|
||||||
Q_EMIT globalNotificationsEnabledChanged();
|
Q_EMIT globalNotificationsEnabledChanged();
|
||||||
Q_EMIT globalNotificationsSetChanged();
|
Q_EMIT globalNotificationsSetChanged();
|
||||||
@@ -95,7 +94,7 @@ void PushRuleModel::updateNotificationRules(const QString &type)
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushRuleModel::setRules(QList<Quotient::PushRule> rules, PushNotificationKind::Kind kind)
|
void PushRuleModel::setRules(QList<Quotient::PushRule> rules, PushRuleKind::Kind kind)
|
||||||
{
|
{
|
||||||
for (const auto &rule : rules) {
|
for (const auto &rule : rules) {
|
||||||
QString roomId;
|
QString roomId;
|
||||||
@@ -128,7 +127,7 @@ int PushRuleModel::getRuleIndex(const QString &ruleId) const
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule rule)
|
PushRuleSection::Section PushRuleModel::getSection(Quotient::PushRule rule)
|
||||||
{
|
{
|
||||||
auto ruleId = rule.ruleId;
|
auto ruleId = rule.ruleId;
|
||||||
|
|
||||||
@@ -136,7 +135,7 @@ PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule ru
|
|||||||
return defaultSections.value(ruleId);
|
return defaultSections.value(ruleId);
|
||||||
} else {
|
} else {
|
||||||
if (rule.ruleId.startsWith(u'.')) {
|
if (rule.ruleId.startsWith(u'.')) {
|
||||||
return PushNotificationSection::Unknown;
|
return PushRuleSection::Unknown;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* If the rule name resolves to a matrix id for a room that the user is part
|
* If the rule name resolves to a matrix id for a room that the user is part
|
||||||
@@ -146,7 +145,7 @@ PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule ru
|
|||||||
* Rooms that the user hasn't joined shouldn't have a rule.
|
* Rooms that the user hasn't joined shouldn't have a rule.
|
||||||
*/
|
*/
|
||||||
if (m_connection->room(ruleId) != nullptr) {
|
if (m_connection->room(ruleId) != nullptr) {
|
||||||
return PushNotificationSection::Undefined;
|
return PushRuleSection::Undefined;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* If the rule name resolves to a matrix id for a user it shouldn't appear
|
* If the rule name resolves to a matrix id for a user it shouldn't appear
|
||||||
@@ -160,26 +159,26 @@ PushNotificationSection::Section PushRuleModel::getSection(Quotient::PushRule ru
|
|||||||
testUserId.prepend(u'@');
|
testUserId.prepend(u'@');
|
||||||
}
|
}
|
||||||
if (testUserId.startsWith(u'@') && !Quotient::serverPart(testUserId).isEmpty() && m_connection->user(testUserId) != nullptr) {
|
if (testUserId.startsWith(u'@') && !Quotient::serverPart(testUserId).isEmpty() && m_connection->user(testUserId) != nullptr) {
|
||||||
return PushNotificationSection::Undefined;
|
return PushRuleSection::Undefined;
|
||||||
}
|
}
|
||||||
// If the rule has push conditions and one is a room ID it is a room only keyword.
|
// If the rule has push conditions and one is a room ID it is a room only keyword.
|
||||||
if (!rule.conditions.isEmpty()) {
|
if (!rule.conditions.isEmpty()) {
|
||||||
for (auto condition : rule.conditions) {
|
for (auto condition : rule.conditions) {
|
||||||
if (condition.key == QStringLiteral("room_id")) {
|
if (condition.key == QStringLiteral("room_id")) {
|
||||||
return PushNotificationSection::RoomKeywords;
|
return PushRuleSection::RoomKeywords;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return PushNotificationSection::Keywords;
|
return PushRuleSection::Keywords;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PushNotificationAction::Action PushRuleModel::defaultState() const
|
PushRuleAction::Action PushRuleModel::defaultState() const
|
||||||
{
|
{
|
||||||
return m_defaultKeywordAction;
|
return m_defaultKeywordAction;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushRuleModel::setDefaultState(PushNotificationAction::Action defaultState)
|
void PushRuleModel::setDefaultState(PushRuleAction::Action defaultState)
|
||||||
{
|
{
|
||||||
if (defaultState == m_defaultKeywordAction) {
|
if (defaultState == m_defaultKeywordAction) {
|
||||||
return;
|
return;
|
||||||
@@ -273,7 +272,7 @@ QHash<int, QByteArray> PushRuleModel::roleNames() const
|
|||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushRuleModel::setPushRuleAction(const QString &id, PushNotificationAction::Action action)
|
void PushRuleModel::setPushRuleAction(const QString &id, PushRuleAction::Action action)
|
||||||
{
|
{
|
||||||
int index = getRuleIndex(id);
|
int index = getRuleIndex(id);
|
||||||
if (index == -1) {
|
if (index == -1) {
|
||||||
@@ -283,22 +282,22 @@ void PushRuleModel::setPushRuleAction(const QString &id, PushNotificationAction:
|
|||||||
auto rule = m_rules[index];
|
auto rule = m_rules[index];
|
||||||
|
|
||||||
// Override rules need to be disabled when off so that other rules can match the message if they apply.
|
// Override rules need to be disabled when off so that other rules can match the message if they apply.
|
||||||
if (action == PushNotificationAction::Off && rule.kind == PushNotificationKind::Override) {
|
if (action == PushRuleAction::Off && rule.kind == PushRuleKind::Override) {
|
||||||
setNotificationRuleEnabled(PushNotificationKind::kindString(rule.kind), rule.id, false);
|
setNotificationRuleEnabled(PushRuleKind::kindString(rule.kind), rule.id, false);
|
||||||
} else if (rule.kind == PushNotificationKind::Override) {
|
} else if (rule.kind == PushRuleKind::Override) {
|
||||||
setNotificationRuleEnabled(PushNotificationKind::kindString(rule.kind), rule.id, true);
|
setNotificationRuleEnabled(PushRuleKind::kindString(rule.kind), rule.id, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
setNotificationRuleActions(PushNotificationKind::kindString(rule.kind), rule.id, action);
|
setNotificationRuleActions(PushRuleKind::kindString(rule.kind), rule.id, action);
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
|
void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
|
||||||
{
|
{
|
||||||
PushNotificationKind::Kind kind = PushNotificationKind::Content;
|
PushRuleKind::Kind kind = PushRuleKind::Content;
|
||||||
const QList<QVariant> actions = actionToVariant(m_defaultKeywordAction);
|
const QList<QVariant> actions = actionToVariant(m_defaultKeywordAction);
|
||||||
QList<Quotient::PushCondition> pushConditions;
|
QList<Quotient::PushCondition> pushConditions;
|
||||||
if (!roomId.isEmpty()) {
|
if (!roomId.isEmpty()) {
|
||||||
kind = PushNotificationKind::Override;
|
kind = PushRuleKind::Override;
|
||||||
|
|
||||||
Quotient::PushCondition roomCondition;
|
Quotient::PushCondition roomCondition;
|
||||||
roomCondition.kind = QStringLiteral("event_match");
|
roomCondition.kind = QStringLiteral("event_match");
|
||||||
@@ -314,7 +313,7 @@ void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto job = m_connection->callApi<Quotient::SetPushRuleJob>(QLatin1String("global"),
|
auto job = m_connection->callApi<Quotient::SetPushRuleJob>(QLatin1String("global"),
|
||||||
PushNotificationKind::kindString(kind),
|
PushRuleKind::kindString(kind),
|
||||||
keyword,
|
keyword,
|
||||||
actions,
|
actions,
|
||||||
QString(),
|
QString(),
|
||||||
@@ -338,7 +337,7 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto kind = PushNotificationKind::kindString(m_rules[index].kind);
|
auto kind = PushRuleKind::kindString(m_rules[index].kind);
|
||||||
auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(QStringLiteral("global"), kind, m_rules[index].id);
|
auto job = m_connection->callApi<Quotient::DeletePushRuleJob>(QStringLiteral("global"), kind, m_rules[index].id);
|
||||||
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
|
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
|
||||||
qWarning() << QLatin1String("Unable to remove push rule for keyword %1: ").arg(m_rules[index].id) << job->errorString();
|
qWarning() << QLatin1String("Unable to remove push rule for keyword %1: ").arg(m_rules[index].id) << job->errorString();
|
||||||
@@ -355,7 +354,7 @@ void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QStrin
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void PushRuleModel::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action)
|
void PushRuleModel::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushRuleAction::Action action)
|
||||||
{
|
{
|
||||||
QList<QVariant> actions;
|
QList<QVariant> actions;
|
||||||
if (ruleId == QStringLiteral(".m.rule.call")) {
|
if (ruleId == QStringLiteral(".m.rule.call")) {
|
||||||
@@ -367,7 +366,7 @@ void PushRuleModel::setNotificationRuleActions(const QString &kind, const QStrin
|
|||||||
m_connection->callApi<Quotient::SetPushRuleActionsJob>(QStringLiteral("global"), kind, ruleId, actions);
|
m_connection->callApi<Quotient::SetPushRuleActionsJob>(QStringLiteral("global"), kind, ruleId, actions);
|
||||||
}
|
}
|
||||||
|
|
||||||
PushNotificationAction::Action PushRuleModel::variantToAction(const QList<QVariant> &actions, bool enabled)
|
PushRuleAction::Action PushRuleModel::variantToAction(const QList<QVariant> &actions, bool enabled)
|
||||||
{
|
{
|
||||||
bool notify = false;
|
bool notify = false;
|
||||||
bool isNoisy = false;
|
bool isNoisy = false;
|
||||||
@@ -392,47 +391,47 @@ PushNotificationAction::Action PushRuleModel::variantToAction(const QList<QVaria
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return PushNotificationAction::Off;
|
return PushRuleAction::Off;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (notify) {
|
if (notify) {
|
||||||
if (isNoisy && highlightEnabled) {
|
if (isNoisy && highlightEnabled) {
|
||||||
return PushNotificationAction::NoisyHighlight;
|
return PushRuleAction::NoisyHighlight;
|
||||||
} else if (isNoisy) {
|
} else if (isNoisy) {
|
||||||
return PushNotificationAction::Noisy;
|
return PushRuleAction::Noisy;
|
||||||
} else if (highlightEnabled) {
|
} else if (highlightEnabled) {
|
||||||
return PushNotificationAction::Highlight;
|
return PushRuleAction::Highlight;
|
||||||
} else {
|
} else {
|
||||||
return PushNotificationAction::On;
|
return PushRuleAction::On;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
return PushNotificationAction::Off;
|
return PushRuleAction::Off;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QVariant> PushRuleModel::actionToVariant(PushNotificationAction::Action action, const QString &sound)
|
QList<QVariant> PushRuleModel::actionToVariant(PushRuleAction::Action action, const QString &sound)
|
||||||
{
|
{
|
||||||
// The caller should never try to set the state to unknown.
|
// The caller should never try to set the state to unknown.
|
||||||
// It exists only as a default state to diable the settings options until the actual state is retrieved from the server.
|
// It exists only as a default state to diable the settings options until the actual state is retrieved from the server.
|
||||||
if (action == PushNotificationAction::Unknown) {
|
if (action == PushRuleAction::Unknown) {
|
||||||
Q_ASSERT(false);
|
Q_ASSERT(false);
|
||||||
return QList<QVariant>();
|
return QList<QVariant>();
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QVariant> actions;
|
QList<QVariant> actions;
|
||||||
|
|
||||||
if (action != PushNotificationAction::Off) {
|
if (action != PushRuleAction::Off) {
|
||||||
actions.append(QStringLiteral("notify"));
|
actions.append(QStringLiteral("notify"));
|
||||||
} else {
|
} else {
|
||||||
actions.append(QStringLiteral("dont_notify"));
|
actions.append(QStringLiteral("dont_notify"));
|
||||||
}
|
}
|
||||||
if (action == PushNotificationAction::Noisy || action == PushNotificationAction::NoisyHighlight) {
|
if (action == PushRuleAction::Noisy || action == PushRuleAction::NoisyHighlight) {
|
||||||
QJsonObject soundTweak;
|
QJsonObject soundTweak;
|
||||||
soundTweak.insert(QStringLiteral("set_tweak"), QStringLiteral("sound"));
|
soundTweak.insert(QStringLiteral("set_tweak"), QStringLiteral("sound"));
|
||||||
soundTweak.insert(QStringLiteral("value"), sound);
|
soundTweak.insert(QStringLiteral("value"), sound);
|
||||||
actions.append(soundTweak);
|
actions.append(soundTweak);
|
||||||
}
|
}
|
||||||
if (action == PushNotificationAction::Highlight || action == PushNotificationAction::NoisyHighlight) {
|
if (action == PushRuleAction::Highlight || action == PushRuleAction::NoisyHighlight) {
|
||||||
QJsonObject highlightTweak;
|
QJsonObject highlightTweak;
|
||||||
highlightTweak.insert(QStringLiteral("set_tweak"), QStringLiteral("highlight"));
|
highlightTweak.insert(QStringLiteral("set_tweak"), QStringLiteral("highlight"));
|
||||||
actions.append(highlightTweak);
|
actions.append(highlightTweak);
|
||||||
|
|||||||
@@ -8,124 +8,8 @@
|
|||||||
|
|
||||||
#include <Quotient/csapi/definitions/push_rule.h>
|
#include <Quotient/csapi/definitions/push_rule.h>
|
||||||
|
|
||||||
#include "notificationsmanager.h"
|
#include "enums/pushrule.h"
|
||||||
|
#include "neochatconnection.h"
|
||||||
/**
|
|
||||||
* @class PushNotificationKind
|
|
||||||
*
|
|
||||||
* A class with the Kind enum for push notifications and helper functions.
|
|
||||||
*
|
|
||||||
* The kind relates to the kinds of push rule definied in the matrix spec, see
|
|
||||||
* https://spec.matrix.org/v1.7/client-server-api/#push-rules for full details.
|
|
||||||
*/
|
|
||||||
class PushNotificationKind : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
QML_UNCREATABLE("")
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Defines the different kinds of push rule.
|
|
||||||
*/
|
|
||||||
enum Kind {
|
|
||||||
Override = 0, /**< The highest priority rules. */
|
|
||||||
Content, /**< These configure behaviour for messages that match certain patterns. */
|
|
||||||
Room, /**< These rules change the behaviour of all messages for a given room. */
|
|
||||||
Sender, /**< These rules configure notification behaviour for messages from a specific Matrix user ID. */
|
|
||||||
Underride, /**< These are identical to override rules, but have a lower priority than content, room and sender rules. */
|
|
||||||
};
|
|
||||||
Q_ENUM(Kind)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Translate the Kind enum value to a human readable string.
|
|
||||||
*
|
|
||||||
* @sa Kind
|
|
||||||
*/
|
|
||||||
static QString kindString(Kind kind)
|
|
||||||
{
|
|
||||||
switch (kind) {
|
|
||||||
case Kind::Override:
|
|
||||||
return QLatin1String("override");
|
|
||||||
case Kind::Content:
|
|
||||||
return QLatin1String("content");
|
|
||||||
case Kind::Room:
|
|
||||||
return QLatin1String("room");
|
|
||||||
case Kind::Sender:
|
|
||||||
return QLatin1String("sender");
|
|
||||||
case Kind::Underride:
|
|
||||||
return QLatin1String("underride");
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @class PushNotificationSection
|
|
||||||
*
|
|
||||||
* A class with the Section enum for push notifications and helper functions.
|
|
||||||
*
|
|
||||||
* @note This is different from the PushNotificationKind and instead is used for sorting
|
|
||||||
* in the settings page which is not necessarily by Kind.
|
|
||||||
*
|
|
||||||
* @sa PushNotificationKind
|
|
||||||
*/
|
|
||||||
class PushNotificationSection : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
QML_UNCREATABLE("")
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Defines the sections to sort push rules into.
|
|
||||||
*/
|
|
||||||
enum Section {
|
|
||||||
Master = 0, /**< The master push rule */
|
|
||||||
Room, /**< Push rules relating to all rooms. */
|
|
||||||
Mentions, /**< Push rules relating to user mentions. */
|
|
||||||
Keywords, /**< Global Keyword push rules. */
|
|
||||||
RoomKeywords, /**< Keyword push rules that only apply to a specific room. */
|
|
||||||
Invites, /**< Push rules relating to invites. */
|
|
||||||
Unknown, /**< New default push rules that have not been added to the model yet. */
|
|
||||||
/**
|
|
||||||
* @brief Push rules that should never be shown.
|
|
||||||
*
|
|
||||||
* There are numerous rules that get set that shouldn't be shown in the general
|
|
||||||
* list e.g. The array of rules used to override global settings in individual
|
|
||||||
* rooms.
|
|
||||||
*
|
|
||||||
* This is specifically different to unknown which are just new default push
|
|
||||||
* rule that haven't been added to the model yet.
|
|
||||||
*/
|
|
||||||
Undefined,
|
|
||||||
};
|
|
||||||
Q_ENUM(Section)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Translate the Section enum value to a human readable string.
|
|
||||||
*
|
|
||||||
* @sa Section
|
|
||||||
*/
|
|
||||||
static QString sectionString(Section section)
|
|
||||||
{
|
|
||||||
switch (section) {
|
|
||||||
case Section::Master:
|
|
||||||
return QLatin1String("Master");
|
|
||||||
case Section::Room:
|
|
||||||
return QLatin1String("Room Notifications");
|
|
||||||
case Section::Mentions:
|
|
||||||
return QLatin1String("@Mentions");
|
|
||||||
case Section::Keywords:
|
|
||||||
return QLatin1String("Keywords");
|
|
||||||
case Section::Invites:
|
|
||||||
return QLatin1String("Invites");
|
|
||||||
default:
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class PushRuleModel
|
* @class PushRuleModel
|
||||||
@@ -140,7 +24,7 @@ class PushRuleModel : public QAbstractListModel
|
|||||||
/**
|
/**
|
||||||
* @brief The default state for any newly created keyword rule.
|
* @brief The default state for any newly created keyword rule.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(PushNotificationAction::Action defaultState READ defaultState WRITE setDefaultState NOTIFY defaultStateChanged)
|
Q_PROPERTY(PushRuleAction::Action defaultState READ defaultState WRITE setDefaultState NOTIFY defaultStateChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The global notification state.
|
* @brief The global notification state.
|
||||||
@@ -153,7 +37,7 @@ class PushRuleModel : public QAbstractListModel
|
|||||||
/**
|
/**
|
||||||
* @brief Whether the global notification state has been retrieved from the server.
|
* @brief Whether the global notification state has been retrieved from the server.
|
||||||
*
|
*
|
||||||
* @sa globalNotificationsEnabled, PushNotificationAction::Action
|
* @sa globalNotificationsEnabled, PushRuleAction::Action
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(bool globalNotificationsSet READ globalNotificationsSet NOTIFY globalNotificationsSetChanged)
|
Q_PROPERTY(bool globalNotificationsSet READ globalNotificationsSet NOTIFY globalNotificationsSetChanged)
|
||||||
|
|
||||||
@@ -162,9 +46,9 @@ class PushRuleModel : public QAbstractListModel
|
|||||||
public:
|
public:
|
||||||
struct Rule {
|
struct Rule {
|
||||||
QString id;
|
QString id;
|
||||||
PushNotificationKind::Kind kind;
|
PushRuleKind::Kind kind;
|
||||||
PushNotificationAction::Action action;
|
PushRuleAction::Action action;
|
||||||
PushNotificationSection::Section section;
|
PushRuleSection::Section section;
|
||||||
bool enabled;
|
bool enabled;
|
||||||
QString roomId;
|
QString roomId;
|
||||||
};
|
};
|
||||||
@@ -176,7 +60,7 @@ public:
|
|||||||
NameRole = Qt::DisplayRole, /**< The push rule name. */
|
NameRole = Qt::DisplayRole, /**< The push rule name. */
|
||||||
IdRole, /**< The push rule ID. */
|
IdRole, /**< The push rule ID. */
|
||||||
KindRole, /**< The kind of notification rule; override, content, etc. */
|
KindRole, /**< The kind of notification rule; override, content, etc. */
|
||||||
ActionRole, /**< The PushNotificationAction for the rule. */
|
ActionRole, /**< The PushRuleAction for the rule. */
|
||||||
HighlightableRole, /**< Whether the rule can have a highlight action. */
|
HighlightableRole, /**< Whether the rule can have a highlight action. */
|
||||||
DeletableRole, /**< Whether the rule can be deleted the rule. */
|
DeletableRole, /**< Whether the rule can be deleted the rule. */
|
||||||
SectionRole, /**< The section to sort into in the settings page. */
|
SectionRole, /**< The section to sort into in the settings page. */
|
||||||
@@ -186,8 +70,8 @@ public:
|
|||||||
|
|
||||||
explicit PushRuleModel(QObject *parent = nullptr);
|
explicit PushRuleModel(QObject *parent = nullptr);
|
||||||
|
|
||||||
[[nodiscard]] PushNotificationAction::Action defaultState() const;
|
[[nodiscard]] PushRuleAction::Action defaultState() const;
|
||||||
void setDefaultState(PushNotificationAction::Action defaultState);
|
void setDefaultState(PushRuleAction::Action defaultState);
|
||||||
|
|
||||||
[[nodiscard]] bool globalNotificationsEnabled() const;
|
[[nodiscard]] bool globalNotificationsEnabled() const;
|
||||||
void setGlobalNotificationsEnabled(bool enabled);
|
void setGlobalNotificationsEnabled(bool enabled);
|
||||||
@@ -215,7 +99,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
Q_INVOKABLE void setPushRuleAction(const QString &id, PushNotificationAction::Action action);
|
Q_INVOKABLE void setPushRuleAction(const QString &id, PushRuleAction::Action action);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Add a new keyword to the model.
|
* @brief Add a new keyword to the model.
|
||||||
@@ -240,18 +124,18 @@ private Q_SLOTS:
|
|||||||
void updateNotificationRules(const QString &type);
|
void updateNotificationRules(const QString &type);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
PushNotificationAction::Action m_defaultKeywordAction;
|
PushRuleAction::Action m_defaultKeywordAction;
|
||||||
QList<Rule> m_rules;
|
QList<Rule> m_rules;
|
||||||
NeoChatConnection *m_connection;
|
NeoChatConnection *m_connection;
|
||||||
|
|
||||||
void setRules(QList<Quotient::PushRule> rules, PushNotificationKind::Kind kind);
|
void setRules(QList<Quotient::PushRule> rules, PushRuleKind::Kind kind);
|
||||||
|
|
||||||
int getRuleIndex(const QString &ruleId) const;
|
int getRuleIndex(const QString &ruleId) const;
|
||||||
PushNotificationSection::Section getSection(Quotient::PushRule rule);
|
PushRuleSection::Section getSection(Quotient::PushRule rule);
|
||||||
|
|
||||||
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
|
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
|
||||||
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action);
|
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushRuleAction::Action action);
|
||||||
PushNotificationAction::Action variantToAction(const QList<QVariant> &actions, bool enabled);
|
PushRuleAction::Action variantToAction(const QList<QVariant> &actions, bool enabled);
|
||||||
QList<QVariant> actionToVariant(PushNotificationAction::Action action, const QString &sound = QStringLiteral("default"));
|
QList<QVariant> actionToVariant(PushRuleAction::Action action, const QString &sound = QStringLiteral("default"));
|
||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(PushRuleModel *)
|
Q_DECLARE_METATYPE(PushRuleModel *)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
|
||||||
#include "reactionmodel.h"
|
#include "reactionmodel.h"
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#ifdef HAVE_ICU
|
#ifdef HAVE_ICU
|
||||||
@@ -15,11 +16,20 @@
|
|||||||
|
|
||||||
#include <Quotient/user.h>
|
#include <Quotient/user.h>
|
||||||
|
|
||||||
ReactionModel::ReactionModel(QObject *parent, QList<Reaction> reactions, Quotient::User *localUser)
|
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, const NeoChatRoom *room)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(nullptr)
|
||||||
, m_localUser(localUser)
|
, m_room(room)
|
||||||
|
, m_event(event)
|
||||||
{
|
{
|
||||||
setReactions(reactions);
|
if (m_event != nullptr && m_room != nullptr) {
|
||||||
|
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
|
||||||
|
if (m_event->id() == eventId) {
|
||||||
|
updateReactions();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
updateReactions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||||
@@ -35,38 +45,11 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
const auto &reaction = m_reactions.at(index.row());
|
const auto &reaction = m_reactions.at(index.row());
|
||||||
|
|
||||||
const auto isEmoji = [](const QString &text) {
|
|
||||||
#ifdef HAVE_ICU
|
|
||||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
|
||||||
int from = 0;
|
|
||||||
while (finder.toNextBoundary() != -1) {
|
|
||||||
auto to = finder.position();
|
|
||||||
if (text[from].isSpace()) {
|
|
||||||
from = to;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto first = text.mid(from, to - from).toUcs4()[0];
|
|
||||||
if (!u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
from = to;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
};
|
|
||||||
|
|
||||||
const auto reactionText = isEmoji(reaction.reaction)
|
|
||||||
? QStringLiteral("<span style=\"font-family: 'emoji';\">") + reaction.reaction + QStringLiteral("</span>")
|
|
||||||
: reaction.reaction;
|
|
||||||
|
|
||||||
if (role == TextContentRole) {
|
if (role == TextContentRole) {
|
||||||
if (reaction.authors.count() > 1) {
|
if (reaction.authors.count() > 1) {
|
||||||
return QStringLiteral("%1 %2").arg(reactionText, QString::number(reaction.authors.count()));
|
return QStringLiteral("%1 %2").arg(reactionText(reaction.reaction), QString::number(reaction.authors.count()));
|
||||||
} else {
|
} else {
|
||||||
return reactionText;
|
return reactionText(reaction.reaction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,7 +80,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
|||||||
"%2 reacted with %3",
|
"%2 reacted with %3",
|
||||||
reaction.authors.count(),
|
reaction.authors.count(),
|
||||||
text,
|
text,
|
||||||
reactionText);
|
reactionText(reaction.reaction));
|
||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -107,7 +90,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
|||||||
|
|
||||||
if (role == HasLocalUser) {
|
if (role == HasLocalUser) {
|
||||||
for (auto author : reaction.authors) {
|
for (auto author : reaction.authors) {
|
||||||
if (author.toMap()[QStringLiteral("id")] == m_localUser->id()) {
|
if (author.toMap()[QStringLiteral("id")] == m_room->localUser()->id()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -123,11 +106,44 @@ int ReactionModel::rowCount(const QModelIndex &parent) const
|
|||||||
return m_reactions.count();
|
return m_reactions.count();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReactionModel::setReactions(QList<Reaction> reactions)
|
void ReactionModel::updateReactions()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
|
||||||
m_reactions.clear();
|
m_reactions.clear();
|
||||||
m_reactions = reactions;
|
|
||||||
|
const auto &annotations = m_room->relatedEvents(*m_event, Quotient::EventRelation::AnnotationType);
|
||||||
|
if (annotations.isEmpty()) {
|
||||||
|
endResetModel();
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
QMap<QString, QList<Quotient::User *>> reactions = {};
|
||||||
|
for (const auto &a : annotations) {
|
||||||
|
if (a->isRedacted()) { // Just in case?
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (const auto &e = eventCast<const Quotient::ReactionEvent>(a)) {
|
||||||
|
reactions[e->key()].append(m_room->user(e->senderId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reactions.isEmpty()) {
|
||||||
|
endResetModel();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto i = reactions.constBegin();
|
||||||
|
while (i != reactions.constEnd()) {
|
||||||
|
QVariantList authors;
|
||||||
|
for (const auto &author : i.value()) {
|
||||||
|
authors.append(m_room->getUser(author));
|
||||||
|
}
|
||||||
|
|
||||||
|
m_reactions.append(ReactionModel::Reaction{i.key(), authors});
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,4 +158,32 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString ReactionModel::reactionText(const QString &text)
|
||||||
|
{
|
||||||
|
const auto isEmoji = [](const QString &text) {
|
||||||
|
#ifdef HAVE_ICU
|
||||||
|
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||||
|
int from = 0;
|
||||||
|
while (finder.toNextBoundary() != -1) {
|
||||||
|
auto to = finder.position();
|
||||||
|
if (text[from].isSpace()) {
|
||||||
|
from = to;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||||
|
if (!u_hasBinaryProperty(first, UCHAR_EMOJI_PRESENTATION)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
from = to;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
};
|
||||||
|
|
||||||
|
return isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||||
|
}
|
||||||
|
|
||||||
#include "moc_reactionmodel.cpp"
|
#include "moc_reactionmodel.cpp"
|
||||||
|
|||||||
@@ -3,8 +3,10 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "neochatroom.h"
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
#include <Quotient/events/reactionevent.h>
|
||||||
|
|
||||||
namespace Quotient
|
namespace Quotient
|
||||||
{
|
{
|
||||||
@@ -20,6 +22,7 @@ class ReactionModel : public QAbstractListModel
|
|||||||
{
|
{
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
@@ -41,7 +44,7 @@ public:
|
|||||||
HasLocalUser, /**< Whether the local user is in the list of authors. */
|
HasLocalUser, /**< Whether the local user is in the list of authors. */
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ReactionModel(QObject *parent = nullptr, QList<Reaction> reactions = {}, Quotient::User *localUser = nullptr);
|
explicit ReactionModel(const Quotient::RoomMessageEvent *event, const NeoChatRoom *room);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the given role value at the given index.
|
* @brief Get the given role value at the given index.
|
||||||
@@ -64,14 +67,12 @@ public:
|
|||||||
*/
|
*/
|
||||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Set the reactions data in the model.
|
|
||||||
*/
|
|
||||||
void setReactions(QList<Reaction> reactions);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
const NeoChatRoom *m_room;
|
||||||
|
const Quotient::RoomMessageEvent *m_event;
|
||||||
QList<Reaction> m_reactions;
|
QList<Reaction> m_reactions;
|
||||||
|
|
||||||
Quotient::User *m_localUser;
|
void updateReactions();
|
||||||
|
static QString reactionText(const QString &text);
|
||||||
};
|
};
|
||||||
Q_DECLARE_METATYPE(ReactionModel *)
|
Q_DECLARE_METATYPE(ReactionModel *)
|
||||||
|
|||||||
@@ -3,15 +3,12 @@
|
|||||||
|
|
||||||
#include "roomlistmodel.h"
|
#include "roomlistmodel.h"
|
||||||
|
|
||||||
#include "controller.h"
|
|
||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include "spacehierarchycache.h"
|
#include "spacehierarchycache.h"
|
||||||
|
|
||||||
#include <Quotient/user.h>
|
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
@@ -23,7 +20,6 @@
|
|||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <utility>
|
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
@@ -349,7 +345,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
|||||||
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
|
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
|
||||||
}
|
}
|
||||||
if (role == SubtitleTextRole) {
|
if (role == SubtitleTextRole) {
|
||||||
if (room->lastEventIsSpoiler()) {
|
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
EventHandler eventHandler;
|
EventHandler eventHandler;
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Quotient/events/roomevent.h>
|
|
||||||
|
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,6 @@
|
|||||||
|
|
||||||
#include "serverlistmodel.h"
|
#include "serverlistmodel.h"
|
||||||
|
|
||||||
#include "controller.h"
|
|
||||||
|
|
||||||
#include <Quotient/connection.h>
|
#include <Quotient/connection.h>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
@@ -13,6 +11,8 @@
|
|||||||
#include <KConfigGroup>
|
#include <KConfigGroup>
|
||||||
#include <KSharedConfig>
|
#include <KSharedConfig>
|
||||||
|
|
||||||
|
#include "neochatconnection.h"
|
||||||
|
|
||||||
ServerListModel::ServerListModel(QObject *parent)
|
ServerListModel::ServerListModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/room.h>
|
#include <Quotient/room.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "neochatconnection.h"
|
||||||
|
|
||||||
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
|
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
|
||||||
: QAbstractItemModel(parent)
|
: QAbstractItemModel(parent)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
#include "spacetreeitem.h"
|
#include "spacetreeitem.h"
|
||||||
|
|
||||||
#include "controller.h"
|
#include "neochatconnection.h"
|
||||||
|
|
||||||
SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
|
SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
|
||||||
SpaceTreeItem *parent,
|
SpaceTreeItem *parent,
|
||||||
|
|||||||
@@ -181,6 +181,7 @@ Name[eu]=Gonbidapen berria
|
|||||||
Name[fi]=Uusi kutsu
|
Name[fi]=Uusi kutsu
|
||||||
Name[fr]=Nouvelle invitation
|
Name[fr]=Nouvelle invitation
|
||||||
Name[gl]=Novo convite
|
Name[gl]=Novo convite
|
||||||
|
Name[hu]=Új meghívó
|
||||||
Name[ia]=Nove invitation
|
Name[ia]=Nove invitation
|
||||||
Name[id]=Undangan Baru
|
Name[id]=Undangan Baru
|
||||||
Name[ie]=Nov invitation
|
Name[ie]=Nov invitation
|
||||||
@@ -200,6 +201,7 @@ Name[ta]=புதிய அழைப்பிதழ்
|
|||||||
Name[tr]=Yeni Davet
|
Name[tr]=Yeni Davet
|
||||||
Name[uk]=Нове запрошення
|
Name[uk]=Нове запрошення
|
||||||
Name[x-test]=xxNew Invitationxx
|
Name[x-test]=xxNew Invitationxx
|
||||||
|
Name[zh_CN]=新邀请
|
||||||
Name[zh_TW]=新邀請
|
Name[zh_TW]=新邀請
|
||||||
Comment=There is a new invitation to a room
|
Comment=There is a new invitation to a room
|
||||||
Comment[ar]=توجد دعوة جديدة
|
Comment[ar]=توجد دعوة جديدة
|
||||||
@@ -216,6 +218,7 @@ Comment[eu]=Gela baterako gonbidapen berri bat dago
|
|||||||
Comment[fi]=Uusi kutsu huoneeseen
|
Comment[fi]=Uusi kutsu huoneeseen
|
||||||
Comment[fr]=Il y a une nouvelle invitation dans un salon.
|
Comment[fr]=Il y a une nouvelle invitation dans un salon.
|
||||||
Comment[gl]=Tes un novo convite para unha sala.
|
Comment[gl]=Tes un novo convite para unha sala.
|
||||||
|
Comment[hu]=Új meghívó érkezett egy szobába
|
||||||
Comment[ia]=Il ha un nove invitation a un sala
|
Comment[ia]=Il ha un nove invitation a un sala
|
||||||
Comment[id]=Ada undangan baru ke sebuah ruangan
|
Comment[id]=Ada undangan baru ke sebuah ruangan
|
||||||
Comment[ie]=Vu have un nov invitation a un chambre
|
Comment[ie]=Vu have un nov invitation a un chambre
|
||||||
@@ -235,5 +238,6 @@ Comment[ta]=ஓர் அரங்கிற்கான புதிய அழ
|
|||||||
Comment[tr]=Bir odaya yeni bir davetiye var
|
Comment[tr]=Bir odaya yeni bir davetiye var
|
||||||
Comment[uk]=У кімнаті нове запрошення
|
Comment[uk]=У кімнаті нове запрошення
|
||||||
Comment[x-test]=xxThere is a new invitation to a roomxx
|
Comment[x-test]=xxThere is a new invitation to a roomxx
|
||||||
|
Comment[zh_CN]=有新的聊天室邀请
|
||||||
Comment[zh_TW]=有新的加入聊天室邀請
|
Comment[zh_TW]=有新的加入聊天室邀請
|
||||||
Action=Popup
|
Action=Popup
|
||||||
|
|||||||
@@ -11,9 +11,6 @@
|
|||||||
<entry name="OpenRoom" type="String">
|
<entry name="OpenRoom" type="String">
|
||||||
<label>Latest opened room</label>
|
<label>Latest opened room</label>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="ActiveConnection" type="String">
|
|
||||||
<label>Latest active connection</label>
|
|
||||||
</entry>
|
|
||||||
<entry name="ColorScheme" type="String">
|
<entry name="ColorScheme" type="String">
|
||||||
<label>Color scheme</label>
|
<label>Color scheme</label>
|
||||||
</entry>
|
</entry>
|
||||||
|
|||||||
@@ -44,6 +44,11 @@ NeoChatConnection::NeoChatConnection(QObject *parent)
|
|||||||
connect(this, &NeoChatConnection::networkError, this, [this]() {
|
connect(this, &NeoChatConnection::networkError, this, [this]() {
|
||||||
setIsOnline(false);
|
setIsOnline(false);
|
||||||
});
|
});
|
||||||
|
connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
|
||||||
|
if (job->error() == BaseJob::UserConsentRequired) {
|
||||||
|
Q_EMIT userConsentRequired(job->errorUrl());
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
||||||
|
|||||||
@@ -99,6 +99,7 @@ Q_SIGNALS:
|
|||||||
void labelChanged();
|
void labelChanged();
|
||||||
void isOnlineChanged();
|
void isOnlineChanged();
|
||||||
void passwordStatus(NeoChatConnection::PasswordStatus status);
|
void passwordStatus(NeoChatConnection::PasswordStatus status);
|
||||||
|
void userConsentRequired(QUrl url);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool m_isOnline = true;
|
bool m_isOnline = true;
|
||||||
|
|||||||
@@ -4,15 +4,10 @@
|
|||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
#include <QGuiApplication>
|
|
||||||
#include <QMetaObject>
|
|
||||||
#include <QMimeDatabase>
|
|
||||||
#include <QPalette>
|
|
||||||
#include <QTemporaryFile>
|
|
||||||
#include <QTextDocument>
|
|
||||||
|
|
||||||
#include <QMediaMetaData>
|
#include <QMediaMetaData>
|
||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
|
#include <QMimeDatabase>
|
||||||
|
#include <QTemporaryFile>
|
||||||
|
|
||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/user.h>
|
#include <Quotient/user.h>
|
||||||
@@ -35,13 +30,11 @@
|
|||||||
#include <Quotient/events/roommemberevent.h>
|
#include <Quotient/events/roommemberevent.h>
|
||||||
#include <Quotient/events/roompowerlevelsevent.h>
|
#include <Quotient/events/roompowerlevelsevent.h>
|
||||||
#include <Quotient/events/simplestateevents.h>
|
#include <Quotient/events/simplestateevents.h>
|
||||||
#include <Quotient/events/stickerevent.h>
|
|
||||||
#include <Quotient/jobs/downloadfilejob.h>
|
#include <Quotient/jobs/downloadfilejob.h>
|
||||||
#include <Quotient/qt_connection_util.h>
|
#include <Quotient/qt_connection_util.h>
|
||||||
|
|
||||||
#include "chatbarcache.h"
|
#include "chatbarcache.h"
|
||||||
#include "clipboard.h"
|
#include "clipboard.h"
|
||||||
#include "controller.h"
|
|
||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
#include "events/joinrulesevent.h"
|
#include "events/joinrulesevent.h"
|
||||||
#include "events/pollevent.h"
|
#include "events/pollevent.h"
|
||||||
@@ -53,10 +46,9 @@
|
|||||||
#include "urlhelper.h"
|
#include "urlhelper.h"
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
#include <KConfig>
|
|
||||||
#include <KConfigGroup>
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
#include <KIO/Job>
|
#include <KIO/Job>
|
||||||
|
#include <KIO/JobTracker>
|
||||||
#endif
|
#endif
|
||||||
#include <KJobTrackerInterface>
|
#include <KJobTrackerInterface>
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
@@ -108,16 +100,14 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto roomMemberEvent = currentState().get<RoomMemberEvent>(localUser()->id());
|
auto roomMemberEvent = currentState().get<RoomMemberEvent>(localUser()->id());
|
||||||
Q_ASSERT(roomMemberEvent);
|
|
||||||
const QString senderId = roomMemberEvent->senderId();
|
|
||||||
QImage avatar_image;
|
QImage avatar_image;
|
||||||
if (!user(senderId)->avatarUrl(this).isEmpty()) {
|
if (roomMemberEvent && !user(roomMemberEvent->senderId())->avatarUrl(this).isEmpty()) {
|
||||||
avatar_image = user(senderId)->avatar(128, this);
|
avatar_image = user(roomMemberEvent->senderId())->avatar(128, this);
|
||||||
} else {
|
} else {
|
||||||
qWarning() << "using this room's avatar";
|
qWarning() << "using this room's avatar";
|
||||||
avatar_image = avatar(128);
|
avatar_image = avatar(128);
|
||||||
}
|
}
|
||||||
NotificationsManager::instance().postInviteNotification(this, displayNameForHtml(), htmlSafeMemberName(senderId), avatar_image);
|
NotificationsManager::instance().postInviteNotification(this, displayName(), htmlSafeMemberName(roomMemberEvent->senderId()), avatar_image);
|
||||||
});
|
});
|
||||||
connect(this, &Room::changed, this, [this] {
|
connect(this, &Room::changed, this, [this] {
|
||||||
Q_EMIT canEncryptRoomChanged();
|
Q_EMIT canEncryptRoomChanged();
|
||||||
@@ -1727,15 +1717,26 @@ bool NeoChatRoom::canEncryptRoom() const
|
|||||||
return !usesEncryption() && canSendState("m.room.encryption"_ls);
|
return !usesEncryption() && canSendState("m.room.encryption"_ls);
|
||||||
}
|
}
|
||||||
|
|
||||||
PollHandler *NeoChatRoom::poll(const QString &eventId)
|
static PollHandler *emptyPollHandler = new PollHandler;
|
||||||
|
|
||||||
|
PollHandler *NeoChatRoom::poll(const QString &eventId) const
|
||||||
{
|
{
|
||||||
|
if (auto pollHandler = m_polls[eventId]) {
|
||||||
|
return pollHandler;
|
||||||
|
}
|
||||||
|
return emptyPollHandler;
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::createPollHandler(const Quotient::PollStartEvent *event)
|
||||||
|
{
|
||||||
|
if (event == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto eventId = event->id();
|
||||||
if (!m_polls.contains(eventId)) {
|
if (!m_polls.contains(eventId)) {
|
||||||
auto handler = new PollHandler(this);
|
auto handler = new PollHandler(this, event);
|
||||||
handler->setRoom(this);
|
|
||||||
handler->setPollStartEventId(eventId);
|
|
||||||
m_polls.insert(eventId, handler);
|
m_polls.insert(eventId, handler);
|
||||||
}
|
}
|
||||||
return m_polls[eventId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool NeoChatRoom::downloadTempFile(const QString &eventId)
|
bool NeoChatRoom::downloadTempFile(const QString &eventId)
|
||||||
|
|||||||
@@ -13,6 +13,8 @@
|
|||||||
#include <QCoroTask>
|
#include <QCoroTask>
|
||||||
#include <Quotient/user.h>
|
#include <Quotient/user.h>
|
||||||
|
|
||||||
|
#include "enums/pushrule.h"
|
||||||
|
#include "events/pollevent.h"
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
|
|
||||||
namespace Quotient
|
namespace Quotient
|
||||||
@@ -22,26 +24,6 @@ class User;
|
|||||||
|
|
||||||
class ChatBarCache;
|
class ChatBarCache;
|
||||||
|
|
||||||
class PushNotificationState : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
QML_UNCREATABLE("")
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Describes the push notification state for the room.
|
|
||||||
*/
|
|
||||||
enum State {
|
|
||||||
Unknown, /**< The state has not yet been obtained from the server. */
|
|
||||||
Default, /**< The room follows the globally configured rules for the local user. */
|
|
||||||
Mute, /**< No notifications for messages in the room. */
|
|
||||||
MentionKeyword, /**< Notifications only for local user mentions and keywords. */
|
|
||||||
All, /**< Notifications for all messages. */
|
|
||||||
};
|
|
||||||
Q_ENUM(State)
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class NeoChatRoom
|
* @class NeoChatRoom
|
||||||
*
|
*
|
||||||
@@ -752,7 +734,14 @@ public:
|
|||||||
*
|
*
|
||||||
* @sa PollHandler
|
* @sa PollHandler
|
||||||
*/
|
*/
|
||||||
Q_INVOKABLE PollHandler *poll(const QString &eventId);
|
PollHandler *poll(const QString &eventId) const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Create a PollHandler object for the given event.
|
||||||
|
*
|
||||||
|
* @sa PollHandler
|
||||||
|
*/
|
||||||
|
void createPollHandler(const Quotient::PollStartEvent *event);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the full Json data for a given room account data event.
|
* @brief Get the full Json data for a given room account data event.
|
||||||
|
|||||||
@@ -21,7 +21,6 @@
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "neochatconfig.h"
|
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
@@ -235,8 +234,9 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
|||||||
notification->sendEvent();
|
notification->sendEvent();
|
||||||
}
|
}
|
||||||
|
|
||||||
void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QString &title, const QString &sender, const QImage &icon)
|
void NotificationsManager::postInviteNotification(NeoChatRoom *rawRoom, const QString &title, const QString &sender, const QImage &icon)
|
||||||
{
|
{
|
||||||
|
QPointer room(rawRoom);
|
||||||
QPixmap img;
|
QPixmap img;
|
||||||
img.convertFromImage(icon);
|
img.convertFromImage(icon);
|
||||||
KNotification *notification = new KNotification(QStringLiteral("invite"));
|
KNotification *notification = new KNotification(QStringLiteral("invite"));
|
||||||
@@ -246,6 +246,9 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QStri
|
|||||||
notification->setFlags(KNotification::Persistent);
|
notification->setFlags(KNotification::Persistent);
|
||||||
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
|
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
|
||||||
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
|
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
|
||||||
|
if (!room) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
WindowController::instance().showAndRaiseWindow(notification->xdgActivationToken());
|
||||||
notification->close();
|
notification->close();
|
||||||
RoomManager::instance().enterRoom(room);
|
RoomManager::instance().enterRoom(room);
|
||||||
|
|||||||
@@ -17,27 +17,6 @@ class NeoChatConnection;
|
|||||||
class KNotification;
|
class KNotification;
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
|
|
||||||
class PushNotificationAction : public QObject
|
|
||||||
{
|
|
||||||
Q_OBJECT
|
|
||||||
QML_ELEMENT
|
|
||||||
QML_UNCREATABLE("")
|
|
||||||
|
|
||||||
public:
|
|
||||||
/**
|
|
||||||
* @brief Defines the global push notification actions.
|
|
||||||
*/
|
|
||||||
enum Action {
|
|
||||||
Unknown = 0, /**< The action has not yet been obtained from the server. */
|
|
||||||
Off, /**< No push notifications are to be sent. */
|
|
||||||
On, /**< Push notifications are on. */
|
|
||||||
Noisy, /**< Push notifications are on, also trigger a notification sound. */
|
|
||||||
Highlight, /**< Push notifications are on, also the event should be highlighted in chat. */
|
|
||||||
NoisyHighlight, /**< Push notifications are on, also trigger a notification sound and highlight in chat. */
|
|
||||||
};
|
|
||||||
Q_ENUM(Action)
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class NotificationsManager
|
* @class NotificationsManager
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -56,6 +56,7 @@ Comment[eu]=Bilatu gelak NeoChat-en
|
|||||||
Comment[fi]=Etsi huoneita NeoChatissä
|
Comment[fi]=Etsi huoneita NeoChatissä
|
||||||
Comment[fr]=Trouver des salons dans NeoChat
|
Comment[fr]=Trouver des salons dans NeoChat
|
||||||
Comment[gl]=Atopa salas en NeoChat.
|
Comment[gl]=Atopa salas en NeoChat.
|
||||||
|
Comment[hu]=Szobák keresése a NeoChatben
|
||||||
Comment[ia]=Trova salas in NeoChat
|
Comment[ia]=Trova salas in NeoChat
|
||||||
Comment[id]=Cari ruangan di NeoChat
|
Comment[id]=Cari ruangan di NeoChat
|
||||||
Comment[ie]=Trovar chambres in NeoChat
|
Comment[ie]=Trovar chambres in NeoChat
|
||||||
@@ -74,6 +75,7 @@ Comment[ta]=நியோச்சாட்டில் அரங்குகள
|
|||||||
Comment[tr]=NeoChat'te odalar bulun
|
Comment[tr]=NeoChat'te odalar bulun
|
||||||
Comment[uk]=Пошук кімнат у NeoChat
|
Comment[uk]=Пошук кімнат у NeoChat
|
||||||
Comment[x-test]=xxFind rooms in NeoChatxx
|
Comment[x-test]=xxFind rooms in NeoChatxx
|
||||||
|
Comment[zh_CN]=在 NeoChat 查找聊天室
|
||||||
Comment[zh_TW]=在 NeoChat 尋找聊天室
|
Comment[zh_TW]=在 NeoChat 尋找聊天室
|
||||||
X-KDE-ServiceTypes=Plasma/Runner
|
X-KDE-ServiceTypes=Plasma/Runner
|
||||||
Type=Service
|
Type=Service
|
||||||
|
|||||||
@@ -3,90 +3,71 @@
|
|||||||
|
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
|
|
||||||
#include "events/pollevent.h"
|
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
#include <Quotient/csapi/relations.h>
|
#include <Quotient/csapi/relations.h>
|
||||||
#include <Quotient/events/roompowerlevelsevent.h>
|
#include <Quotient/events/roompowerlevelsevent.h>
|
||||||
#include <Quotient/user.h>
|
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
PollHandler::PollHandler(QObject *parent)
|
PollHandler::PollHandler(NeoChatRoom *room, const Quotient::PollStartEvent *pollStartEvent)
|
||||||
: QObject(parent)
|
: QObject(room)
|
||||||
|
, m_pollStartEvent(pollStartEvent)
|
||||||
{
|
{
|
||||||
connect(this, &PollHandler::roomChanged, this, &PollHandler::checkLoadRelations);
|
if (room != nullptr && m_pollStartEvent != nullptr) {
|
||||||
connect(this, &PollHandler::pollStartEventIdChanged, this, &PollHandler::checkLoadRelations);
|
connect(room, &NeoChatRoom::aboutToAddNewMessages, this, &PollHandler::updatePoll);
|
||||||
|
checkLoadRelations();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
NeoChatRoom *PollHandler::room() const
|
void PollHandler::updatePoll(Quotient::RoomEventsRange events)
|
||||||
{
|
{
|
||||||
return m_room;
|
// This function will never be called if the PollHandler was not initialized with
|
||||||
}
|
// a NeoChatRoom as parent and a PollStartEvent so no need to null check.
|
||||||
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
void PollHandler::setRoom(NeoChatRoom *room)
|
for (const auto &event : events) {
|
||||||
{
|
if (event->is<PollEndEvent>()) {
|
||||||
if (m_room == room) {
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
return;
|
if (!plEvent) {
|
||||||
}
|
continue;
|
||||||
if (m_room) {
|
|
||||||
disconnect(m_room, nullptr, this, nullptr);
|
|
||||||
}
|
|
||||||
connect(room, &NeoChatRoom::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
|
|
||||||
for (const auto &event : events) {
|
|
||||||
if (event->is<PollEndEvent>()) {
|
|
||||||
auto plEvent = m_room->currentState().get<RoomPowerLevelsEvent>();
|
|
||||||
if (!plEvent) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
auto userPl = plEvent->powerLevelForUser(event->senderId());
|
|
||||||
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= plEvent->redact()) {
|
|
||||||
m_hasEnded = true;
|
|
||||||
m_endedTimestamp = event->originTimestamp();
|
|
||||||
Q_EMIT hasEndedChanged();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (event->is<PollResponseEvent>()) {
|
auto userPl = plEvent->powerLevelForUser(event->senderId());
|
||||||
handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
|
if (event->senderId() == m_pollStartEvent->senderId() || userPl >= plEvent->redact()) {
|
||||||
|
m_hasEnded = true;
|
||||||
|
m_endedTimestamp = event->originTimestamp();
|
||||||
|
Q_EMIT hasEndedChanged();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
if (event->is<PollResponseEvent>()) {
|
||||||
m_room = room;
|
handleAnswer(event->contentJson(), event->senderId(), event->originTimestamp());
|
||||||
Q_EMIT roomChanged();
|
}
|
||||||
}
|
if (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||||
|
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.replace"_ls
|
||||||
QString PollHandler::pollStartEventId() const
|
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString() == m_pollStartEvent->id()) {
|
||||||
{
|
Q_EMIT questionChanged();
|
||||||
return m_pollStartEventId;
|
Q_EMIT optionsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PollHandler::setPollStartEventId(const QString &eventId)
|
|
||||||
{
|
|
||||||
if (eventId == m_pollStartEventId) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
m_pollStartEventId = eventId;
|
|
||||||
Q_EMIT pollStartEventIdChanged();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PollHandler::checkLoadRelations()
|
void PollHandler::checkLoadRelations()
|
||||||
{
|
{
|
||||||
if (!m_room || m_pollStartEventId.isEmpty()) {
|
// This function will never be called if the PollHandler was not initialized with
|
||||||
return;
|
// a NeoChatRoom as parent and a PollStartEvent so no need to null check.
|
||||||
}
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
m_maxVotes = eventCast<const PollStartEvent>(&**m_room->findInTimeline(m_pollStartEventId))->maxSelections();
|
m_maxVotes = m_pollStartEvent->maxSelections();
|
||||||
auto job = m_room->connection()->callApi<GetRelatingEventsJob>(m_room->id(), m_pollStartEventId);
|
auto job = room->connection()->callApi<GetRelatingEventsJob>(room->id(), m_pollStartEvent->id());
|
||||||
connect(job, &BaseJob::success, this, [this, job]() {
|
connect(job, &BaseJob::success, this, [this, job, room]() {
|
||||||
for (const auto &event : job->chunk()) {
|
for (const auto &event : job->chunk()) {
|
||||||
if (event->is<PollEndEvent>()) {
|
if (event->is<PollEndEvent>()) {
|
||||||
auto plEvent = m_room->currentState().get<RoomPowerLevelsEvent>();
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
if (!plEvent) {
|
if (!plEvent) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
auto userPl = plEvent->powerLevelForUser(event->senderId());
|
auto userPl = plEvent->powerLevelForUser(event->senderId());
|
||||||
if (event->senderId() == (*m_room->findInTimeline(m_pollStartEventId))->senderId() || userPl >= plEvent->redact()) {
|
if (event->senderId() == m_pollStartEvent->senderId() || userPl >= plEvent->redact()) {
|
||||||
m_hasEnded = true;
|
m_hasEnded = true;
|
||||||
m_endedTimestamp = event->originTimestamp();
|
m_endedTimestamp = event->originTimestamp();
|
||||||
Q_EMIT hasEndedChanged();
|
Q_EMIT hasEndedChanged();
|
||||||
@@ -123,6 +104,22 @@ void PollHandler::handleAnswer(const QJsonObject &content, const QString &sender
|
|||||||
Q_EMIT answersChanged();
|
Q_EMIT answersChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString PollHandler::question() const
|
||||||
|
{
|
||||||
|
if (m_pollStartEvent == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_ls)["question"_ls].toObject()["body"_ls].toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
QJsonArray PollHandler::options() const
|
||||||
|
{
|
||||||
|
if (m_pollStartEvent == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_ls)["answers"_ls].toArray();
|
||||||
|
}
|
||||||
|
|
||||||
QJsonObject PollHandler::answers() const
|
QJsonObject PollHandler::answers() const
|
||||||
{
|
{
|
||||||
return m_answers;
|
return m_answers;
|
||||||
@@ -139,12 +136,25 @@ QJsonObject PollHandler::counts() const
|
|||||||
return counts;
|
return counts;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString PollHandler::kind() const
|
||||||
|
{
|
||||||
|
if (m_pollStartEvent == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return m_pollStartEvent->contentPart<QJsonObject>("org.matrix.msc3381.poll.start"_ls)["kind"_ls].toString();
|
||||||
|
}
|
||||||
|
|
||||||
void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
|
void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId)
|
||||||
{
|
{
|
||||||
Q_ASSERT(eventId.length() > 0);
|
Q_ASSERT(eventId.length() > 0);
|
||||||
Q_ASSERT(answerId.length() > 0);
|
Q_ASSERT(answerId.length() > 0);
|
||||||
|
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||||
|
if (room == nullptr) {
|
||||||
|
qWarning() << "PollHandler is empty, cannot send an answer.";
|
||||||
|
return;
|
||||||
|
}
|
||||||
QStringList ownAnswers;
|
QStringList ownAnswers;
|
||||||
for (const auto &answer : m_answers[m_room->localUser()->id()].toArray()) {
|
for (const auto &answer : m_answers[room->localUser()->id()].toArray()) {
|
||||||
ownAnswers += answer.toString();
|
ownAnswers += answer.toString();
|
||||||
}
|
}
|
||||||
if (ownAnswers.contains(answerId)) {
|
if (ownAnswers.contains(answerId)) {
|
||||||
@@ -159,8 +169,8 @@ void PollHandler::sendPollAnswer(const QString &eventId, const QString &answerId
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto response = new PollResponseEvent(eventId, ownAnswers);
|
auto response = new PollResponseEvent(eventId, ownAnswers);
|
||||||
handleAnswer(response->contentJson(), m_room->localUser()->id(), QDateTime::currentDateTime());
|
handleAnswer(response->contentJson(), room->localUser()->id(), QDateTime::currentDateTime());
|
||||||
m_room->postEvent(response);
|
room->postEvent(response);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PollHandler::hasEnded() const
|
bool PollHandler::hasEnded() const
|
||||||
|
|||||||
@@ -3,11 +3,16 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <QJsonArray>
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPair>
|
#include <QPair>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <Quotient/events/roomevent.h>
|
||||||
|
|
||||||
|
#include "events/pollevent.h"
|
||||||
|
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -27,17 +32,17 @@ class PollHandler : public QObject
|
|||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The current room for the poll.
|
* @brief The question for the poll.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
Q_PROPERTY(QString question READ question NOTIFY questionChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The Matrix event ID for the event that started the poll.
|
* @brief The list of possible answers to the poll.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QString pollStartEventId READ pollStartEventId WRITE setPollStartEventId NOTIFY pollStartEventIdChanged)
|
Q_PROPERTY(QJsonArray options READ options NOTIFY optionsChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The list of answers to the poll from users in the room.
|
* @brief The list of answer responses to the poll from users in the room.
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QJsonObject answers READ answers NOTIFY answersChanged)
|
Q_PROPERTY(QJsonObject answers READ answers NOTIFY answersChanged)
|
||||||
|
|
||||||
@@ -56,20 +61,23 @@ class PollHandler : public QObject
|
|||||||
*/
|
*/
|
||||||
Q_PROPERTY(int answerCount READ answerCount NOTIFY answersChanged)
|
Q_PROPERTY(int answerCount READ answerCount NOTIFY answersChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief The kind of the poll.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString kind READ kind CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
PollHandler(QObject *parent = nullptr);
|
PollHandler() = default;
|
||||||
|
PollHandler(NeoChatRoom *room, const Quotient::PollStartEvent *pollStartEvent);
|
||||||
NeoChatRoom *room() const;
|
|
||||||
void setRoom(NeoChatRoom *room);
|
|
||||||
|
|
||||||
QString pollStartEventId() const;
|
|
||||||
void setPollStartEventId(const QString &eventId);
|
|
||||||
|
|
||||||
bool hasEnded() const;
|
bool hasEnded() const;
|
||||||
int answerCount() const;
|
int answerCount() const;
|
||||||
|
|
||||||
|
QString question() const;
|
||||||
|
QJsonArray options() const;
|
||||||
QJsonObject answers() const;
|
QJsonObject answers() const;
|
||||||
QJsonObject counts() const;
|
QJsonObject counts() const;
|
||||||
|
QString kind() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Send an answer to the poll.
|
* @brief Send an answer to the poll.
|
||||||
@@ -77,14 +85,15 @@ public:
|
|||||||
Q_INVOKABLE void sendPollAnswer(const QString &eventId, const QString &answerId);
|
Q_INVOKABLE void sendPollAnswer(const QString &eventId, const QString &answerId);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void roomChanged();
|
void questionChanged();
|
||||||
void pollStartEventIdChanged();
|
void optionsChanged();
|
||||||
void answersChanged();
|
void answersChanged();
|
||||||
void hasEndedChanged();
|
void hasEndedChanged();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
NeoChatRoom *m_room = nullptr;
|
const Quotient::PollStartEvent *m_pollStartEvent;
|
||||||
QString m_pollStartEventId;
|
|
||||||
|
void updatePoll(Quotient::RoomEventsRange events);
|
||||||
|
|
||||||
void checkLoadRelations();
|
void checkLoadRelations();
|
||||||
void handleAnswer(const QJsonObject &object, const QString &sender, QDateTime timestamp);
|
void handleAnswer(const QJsonObject &object, const QString &sender, QDateTime timestamp);
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user