Compare commits
122 Commits
work/tobia
...
v24.07.90
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0c1494e74a | ||
|
|
18d4da8b42 | ||
|
|
9680cbbb38 | ||
|
|
3b5ba470b6 | ||
|
|
e1066aede2 | ||
|
|
2bf4ed26f0 | ||
|
|
b460b588a8 | ||
|
|
05270c38bf | ||
|
|
18afad83db | ||
|
|
d390433b2b | ||
|
|
0017be1c0f | ||
|
|
63eda3796d | ||
|
|
3246076a0b | ||
|
|
e905cdd151 | ||
|
|
0372074beb | ||
|
|
09e97f2bdb | ||
|
|
8e324c16f3 | ||
|
|
2bb55eece7 | ||
|
|
2877e40647 | ||
|
|
768ec242fa | ||
|
|
194751627f | ||
|
|
bc701c51d9 | ||
|
|
35939b4af4 | ||
|
|
a178b8b6ca | ||
|
|
a6994318de | ||
|
|
5022ed1a63 | ||
|
|
799ffa18c4 | ||
|
|
6323e27040 | ||
|
|
03acb26109 | ||
|
|
34fc1f6a6b | ||
|
|
042032ec46 | ||
|
|
73de99f661 | ||
|
|
cc068f9ebb | ||
|
|
2bfc2b1944 | ||
|
|
029936112f | ||
|
|
d5e400e8a4 | ||
|
|
9dbb9c3f3b | ||
|
|
a101e6b0a7 | ||
|
|
c525ea55ce | ||
|
|
8ca45f298f | ||
|
|
cb4c6cb677 | ||
|
|
d574a97a35 | ||
|
|
20f9a86ad9 | ||
|
|
a4a411cf1f | ||
|
|
029bda5734 | ||
|
|
0fd578e6aa | ||
|
|
95e1bee5e6 | ||
|
|
c8dc10f311 | ||
|
|
a00f4e393b | ||
|
|
5a7dea8857 | ||
|
|
27e8970fff | ||
|
|
7848274ba1 | ||
|
|
145bf0298b | ||
|
|
0392a33b54 | ||
|
|
b211f46e3e | ||
|
|
709711c3ca | ||
|
|
841607406f | ||
|
|
70b726b04d | ||
|
|
2799248106 | ||
|
|
2321299084 | ||
|
|
aa00773a3b | ||
|
|
733de1d0e1 | ||
|
|
c16e4ad412 | ||
|
|
d1cf7a07f1 | ||
|
|
8751f6fea7 | ||
|
|
ea1b577ec7 | ||
|
|
a41afa70eb | ||
|
|
e11c97cdc0 | ||
|
|
d76e44512e | ||
|
|
fd1931377d | ||
|
|
68ce6c4ed7 | ||
|
|
a2ee330307 | ||
|
|
c3a17f951e | ||
|
|
443c0f34d7 | ||
|
|
8725600368 | ||
|
|
3b83b7f190 | ||
|
|
e0c3a1c143 | ||
|
|
bec1ad7bee | ||
|
|
9e6c00f78c | ||
|
|
abbbd9d705 | ||
|
|
4a5c012f05 | ||
|
|
319862b3d4 | ||
|
|
1fcf58024d | ||
|
|
9db162d0fc | ||
|
|
13b15390c3 | ||
|
|
799b62e9d2 | ||
|
|
d91ed535ad | ||
|
|
c48b9874bf | ||
|
|
6a788f6c32 | ||
|
|
53519a604e | ||
|
|
2818b87f02 | ||
|
|
50e10133b9 | ||
|
|
068161719e | ||
|
|
c8d5d095e0 | ||
|
|
52d07320ef | ||
|
|
158b9ea2ca | ||
|
|
6a100bfbab | ||
|
|
b3bd6ee176 | ||
|
|
04696b27eb | ||
|
|
332937dbc1 | ||
|
|
92d932ce5c | ||
|
|
af3c4f536a | ||
|
|
b98ca5af52 | ||
|
|
a15a11f44b | ||
|
|
1460132772 | ||
|
|
76d68bb3e4 | ||
|
|
08df0ebbea | ||
|
|
e918db2cc1 | ||
|
|
2e0dc8db94 | ||
|
|
b78a9f2a9c | ||
|
|
832e6b9de0 | ||
|
|
feec7ca408 | ||
|
|
408f0a12e2 | ||
|
|
35ab4e6e09 | ||
|
|
430bafafe7 | ||
|
|
58d727350d | ||
|
|
c6a4057659 | ||
|
|
60ac806690 | ||
|
|
e9263fc596 | ||
|
|
3dd28a0382 | ||
|
|
066cb8184e | ||
|
|
373e22b999 |
@@ -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 "07")
|
set(RELEASE_SERVICE_VERSION_MINOR "07")
|
||||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
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})
|
||||||
@@ -60,6 +60,9 @@ set_package_properties(Qt6 PROPERTIES
|
|||||||
)
|
)
|
||||||
|
|
||||||
qt_policy(SET QTP0001 NEW)
|
qt_policy(SET QTP0001 NEW)
|
||||||
|
if (QT_KNOWN_POLICY_QTP0004)
|
||||||
|
qt_policy(SET QTP0004 NEW)
|
||||||
|
endif ()
|
||||||
|
|
||||||
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
|
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
|
||||||
set_package_properties(KF6 PROPERTIES
|
set_package_properties(KF6 PROPERTIES
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QTest>
|
#include <QTest>
|
||||||
|
|
||||||
|
#include <Quotient/roommember.h>
|
||||||
#include <Quotient/syncdata.h>
|
#include <Quotient/syncdata.h>
|
||||||
#include <qtestcase.h>
|
#include <qtestcase.h>
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ void ChatBarCacheTest::empty()
|
|||||||
QCOMPARE(chatBarCache->replyId(), QString());
|
QCOMPARE(chatBarCache->replyId(), QString());
|
||||||
QCOMPARE(chatBarCache->isEditing(), false);
|
QCOMPARE(chatBarCache->isEditing(), false);
|
||||||
QCOMPARE(chatBarCache->editId(), QString());
|
QCOMPARE(chatBarCache->editId(), QString());
|
||||||
QCOMPARE(chatBarCache->relationUser(), room->getUser(QString()));
|
QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
|
||||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||||
}
|
}
|
||||||
@@ -64,7 +65,7 @@ void ChatBarCacheTest::noRoom()
|
|||||||
// ChatBarCache has no parent.
|
// ChatBarCache has no parent.
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
||||||
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
|
QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember());
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.");
|
||||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||||
@@ -80,7 +81,7 @@ void ChatBarCacheTest::badParent()
|
|||||||
// ChatBarCache has no parent.
|
// ChatBarCache has no parent.
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
||||||
QCOMPARE(chatBarCache->relationUser(), QVariantMap());
|
QCOMPARE(chatBarCache->relationUser(), Quotient::RoomMember());
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
QTest::ignoreMessage(QtWarningMsg, "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.");
|
||||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||||
@@ -98,7 +99,7 @@ void ChatBarCacheTest::reply()
|
|||||||
QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org"));
|
QCOMPARE(chatBarCache->replyId(), QLatin1String("$153456789:example.org"));
|
||||||
QCOMPARE(chatBarCache->isEditing(), false);
|
QCOMPARE(chatBarCache->isEditing(), false);
|
||||||
QCOMPARE(chatBarCache->editId(), QString());
|
QCOMPARE(chatBarCache->editId(), QString());
|
||||||
QCOMPARE(chatBarCache->relationUser(), room->getUser(QLatin1String("@example:example.org")));
|
QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org")));
|
||||||
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
|
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
|
||||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||||
}
|
}
|
||||||
@@ -115,7 +116,7 @@ void ChatBarCacheTest::edit()
|
|||||||
QCOMPARE(chatBarCache->replyId(), QString());
|
QCOMPARE(chatBarCache->replyId(), QString());
|
||||||
QCOMPARE(chatBarCache->isEditing(), true);
|
QCOMPARE(chatBarCache->isEditing(), true);
|
||||||
QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
|
QCOMPARE(chatBarCache->editId(), QLatin1String("$153456789:example.org"));
|
||||||
QCOMPARE(chatBarCache->relationUser(), room->getUser(QLatin1String("@example:example.org")));
|
QCOMPARE(chatBarCache->relationUser(), room->member(QLatin1String("@example:example.org")));
|
||||||
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
|
QCOMPARE(chatBarCache->relationMessage(), QLatin1String("This is an example\ntext message"));
|
||||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||||
}
|
}
|
||||||
@@ -132,7 +133,7 @@ void ChatBarCacheTest::attachment()
|
|||||||
QCOMPARE(chatBarCache->replyId(), QString());
|
QCOMPARE(chatBarCache->replyId(), QString());
|
||||||
QCOMPARE(chatBarCache->isEditing(), false);
|
QCOMPARE(chatBarCache->isEditing(), false);
|
||||||
QCOMPARE(chatBarCache->editId(), QString());
|
QCOMPARE(chatBarCache->editId(), QString());
|
||||||
QCOMPARE(chatBarCache->relationUser(), room->getUser(QString()));
|
QCOMPARE(chatBarCache->relationUser(), room->member(QString()));
|
||||||
QCOMPARE(chatBarCache->relationMessage(), QString());
|
QCOMPARE(chatBarCache->relationMessage(), QString());
|
||||||
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
|
QCOMPARE(chatBarCache->attachmentPath(), QLatin1String("some/path"));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -25,7 +25,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"user_ids": [
|
"user_ids": [
|
||||||
"@alice:matrix.org",
|
"@alice:matrix.org",
|
||||||
"@bob:example.com"
|
"@bob:kde.org"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"$153456789:example.org": {
|
"$153456789:example.org": {
|
||||||
"m.read": {
|
"m.read": {
|
||||||
"@alice:matrix.org": {
|
"@alice:example.org": {
|
||||||
"ts": 1436451550453
|
"ts": 1436451550453
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -47,7 +47,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"$1532735824654:example.org": {
|
"$1532735824654:example.org": {
|
||||||
"m.read": {
|
"m.read": {
|
||||||
"@bob:example.com": {
|
"@bob:kde.org": {
|
||||||
"ts": 1436451550453
|
"ts": 1436451550453
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,6 +67,18 @@
|
|||||||
},
|
},
|
||||||
"type": "m.receipt"
|
"type": "m.receipt"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"$1532735824654:example.org": {
|
||||||
|
"m.read": {
|
||||||
|
"@tim2:example.com": {
|
||||||
|
"ts": 1436451550454
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"type": "m.receipt"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"$1532735824654:example.org": {
|
"$1532735824654:example.org": {
|
||||||
@@ -136,6 +148,22 @@
|
|||||||
"age": 1234
|
"age": 1234
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||||
|
"displayname": "Bob",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "bob:kde.org",
|
||||||
|
"state_key": "@bob:kde.org",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"displayname": "Look\nat\nme\nI\nput\nnewlines\nin\nmy\ndisplay name",
|
"displayname": "Look\nat\nme\nI\nput\nnewlines\nin\nmy\ndisplay name",
|
||||||
@@ -156,7 +184,7 @@
|
|||||||
"summary": {
|
"summary": {
|
||||||
"m.heroes": [
|
"m.heroes": [
|
||||||
"@alice:example.com",
|
"@alice:example.com",
|
||||||
"@bob:example.com"
|
"@bob:kde.org"
|
||||||
],
|
],
|
||||||
"m.invited_member_count": 0,
|
"m.invited_member_count": 0,
|
||||||
"m.joined_member_count": 2
|
"m.joined_member_count": 2
|
||||||
|
|||||||
@@ -37,16 +37,14 @@
|
|||||||
"events": [
|
"events": [
|
||||||
{
|
{
|
||||||
"content": {
|
"content": {
|
||||||
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
"displayname": "Example",
|
||||||
"displayname": "Alice Margatroid",
|
"membership": "join"
|
||||||
"membership": "join",
|
|
||||||
"reason": "Looking for support"
|
|
||||||
},
|
},
|
||||||
"event_id": "$143273582443PhrSn:example.org",
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
"origin_server_ts": 1432735824653,
|
"origin_server_ts": 1432735824653,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"sender": "@example:example.org",
|
"sender": "@example:example.org",
|
||||||
"state_key": "@alice:example.org",
|
"state_key": "@example:example.org",
|
||||||
"type": "m.room.member",
|
"type": "m.room.member",
|
||||||
"unsigned": {
|
"unsigned": {
|
||||||
"age": 1234
|
"age": 1234
|
||||||
|
|||||||
@@ -130,7 +130,23 @@
|
|||||||
"origin_server_ts": 1432735824653,
|
"origin_server_ts": 1432735824653,
|
||||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
"sender": "@example:example.org",
|
"sender": "@example:example.org",
|
||||||
"state_key": "@alice:example.org",
|
"state_key": "@alice:matrix.org",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
|
||||||
|
"displayname": "Bob",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "bob:example.org",
|
||||||
|
"state_key": "@bob:example.org",
|
||||||
"type": "m.room.member",
|
"type": "m.room.member",
|
||||||
"unsigned": {
|
"unsigned": {
|
||||||
"age": 1234
|
"age": 1234
|
||||||
|
|||||||
@@ -51,6 +51,21 @@
|
|||||||
"unsigned": {
|
"unsigned": {
|
||||||
"age": 1234
|
"age": 1234
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"content": {
|
||||||
|
"displayname": "Example",
|
||||||
|
"membership": "join"
|
||||||
|
},
|
||||||
|
"event_id": "$143273582443PhrSn:example.org",
|
||||||
|
"origin_server_ts": 1432735824653,
|
||||||
|
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||||
|
"sender": "@example:example.org",
|
||||||
|
"state_key": "@example:example.org",
|
||||||
|
"type": "m.room.member",
|
||||||
|
"unsigned": {
|
||||||
|
"age": 1234
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -36,8 +36,6 @@ private Q_SLOTS:
|
|||||||
|
|
||||||
void eventId();
|
void eventId();
|
||||||
void nullEventId();
|
void nullEventId();
|
||||||
void author();
|
|
||||||
void nullAuthor();
|
|
||||||
void authorDisplayName();
|
void authorDisplayName();
|
||||||
void nullAuthorDisplayName();
|
void nullAuthorDisplayName();
|
||||||
void singleLineSidplayName();
|
void singleLineSidplayName();
|
||||||
@@ -75,8 +73,6 @@ private Q_SLOTS:
|
|||||||
void nullThread();
|
void nullThread();
|
||||||
void location();
|
void location();
|
||||||
void nullLocation();
|
void nullLocation();
|
||||||
void readMarkers();
|
|
||||||
void nullReadMarkers();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
void EventHandlerTest::initTestCase()
|
void EventHandlerTest::initTestCase()
|
||||||
@@ -98,33 +94,6 @@ void EventHandlerTest::nullEventId()
|
|||||||
QCOMPARE(noEventHandler.getId(), QString());
|
QCOMPARE(noEventHandler.getId(), QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandlerTest::author()
|
|
||||||
{
|
|
||||||
auto event = room->messageEvents().at(0).get();
|
|
||||||
auto author = room->member(event->senderId());
|
|
||||||
EventHandler eventHandler(room, event);
|
|
||||||
|
|
||||||
auto eventHandlerAuthor = eventHandler.getAuthor();
|
|
||||||
|
|
||||||
QCOMPARE(eventHandlerAuthor["isLocalUser"_ls], author.id() == room->localMember().id());
|
|
||||||
QCOMPARE(eventHandlerAuthor["id"_ls], author.id());
|
|
||||||
QCOMPARE(eventHandlerAuthor["displayName"_ls], author.displayName());
|
|
||||||
QCOMPARE(eventHandlerAuthor["avatarSource"_ls], room->avatarForMember(author));
|
|
||||||
QCOMPARE(eventHandlerAuthor["avatarMediaId"_ls], author.avatarMediaId());
|
|
||||||
QCOMPARE(eventHandlerAuthor["color"_ls], Utils::getUserColor(author.hueF()));
|
|
||||||
QCOMPARE(eventHandlerAuthor["object"_ls], QVariant::fromValue(author));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventHandlerTest::nullAuthor()
|
|
||||||
{
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr.");
|
|
||||||
QCOMPARE(emptyHandler.getAuthor(), QVariantMap());
|
|
||||||
|
|
||||||
EventHandler noEventHandler(room, nullptr);
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user.");
|
|
||||||
QCOMPARE(noEventHandler.getAuthor(), room->getUser(QString()));
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventHandlerTest::authorDisplayName()
|
void EventHandlerTest::authorDisplayName()
|
||||||
{
|
{
|
||||||
EventHandler eventHandler(room, room->messageEvents().at(1).get());
|
EventHandler eventHandler(room, room->messageEvents().at(1).get());
|
||||||
@@ -194,6 +163,7 @@ void EventHandlerTest::timeString()
|
|||||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
|
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
|
||||||
QCOMPARE(eventHandler.getTimeString(true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
QCOMPARE(eventHandler.getTimeString(true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
|
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
|
||||||
|
QCOMPARE(eventHandler.getTimeString(QStringLiteral("hh:mm")), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandlerTest::nullTimeString()
|
void EventHandlerTest::nullTimeString()
|
||||||
@@ -398,26 +368,25 @@ void EventHandlerTest::replyAuthor()
|
|||||||
|
|
||||||
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
|
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
|
||||||
|
|
||||||
QCOMPARE(eventHandlerReplyAuthor["isLocalUser"_ls], replyAuthor.id() == room->localMember().id());
|
QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id());
|
||||||
QCOMPARE(eventHandlerReplyAuthor["id"_ls], replyAuthor.id());
|
QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id());
|
||||||
QCOMPARE(eventHandlerReplyAuthor["displayName"_ls], replyAuthor.displayName());
|
QCOMPARE(eventHandlerReplyAuthor.displayName(), replyAuthor.displayName());
|
||||||
QCOMPARE(eventHandlerReplyAuthor["avatarSource"_ls], room->avatarForMember(replyAuthor));
|
QCOMPARE(eventHandlerReplyAuthor.avatarUrl(), replyAuthor.avatarUrl());
|
||||||
QCOMPARE(eventHandlerReplyAuthor["avatarMediaId"_ls], replyAuthor.avatarMediaId());
|
QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId());
|
||||||
QCOMPARE(eventHandlerReplyAuthor["color"_ls], Utils::getUserColor(replyAuthor.hueF()));
|
QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color());
|
||||||
QCOMPARE(eventHandlerReplyAuthor["object"_ls], QVariant::fromValue(replyAuthor));
|
|
||||||
|
|
||||||
EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
|
EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
|
||||||
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), room->getUser(QString()));
|
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), RoomMember());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandlerTest::nullReplyAuthor()
|
void EventHandlerTest::nullReplyAuthor()
|
||||||
{
|
{
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
|
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
|
||||||
QCOMPARE(emptyHandler.getReplyAuthor(), QVariantMap());
|
QCOMPARE(emptyHandler.getReplyAuthor(), RoomMember());
|
||||||
|
|
||||||
EventHandler noEventHandler(room, nullptr);
|
EventHandler noEventHandler(room, nullptr);
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user.");
|
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user.");
|
||||||
QCOMPARE(noEventHandler.getReplyAuthor(), room->getUser(QString()));
|
QCOMPARE(noEventHandler.getReplyAuthor(), RoomMember());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandlerTest::replyBody()
|
void EventHandlerTest::replyBody()
|
||||||
@@ -523,59 +492,5 @@ void EventHandlerTest::nullLocation()
|
|||||||
QCOMPARE(emptyHandler.getLocationAssetType(), QString());
|
QCOMPARE(emptyHandler.getLocationAssetType(), QString());
|
||||||
}
|
}
|
||||||
|
|
||||||
void EventHandlerTest::readMarkers()
|
|
||||||
{
|
|
||||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
|
||||||
QCOMPARE(eventHandler.hasReadMarkers(), true);
|
|
||||||
|
|
||||||
auto readMarkers = eventHandler.getReadMarkers();
|
|
||||||
|
|
||||||
QCOMPARE(readMarkers.size(), 1);
|
|
||||||
QCOMPARE(readMarkers[0].toMap()["id"_ls], QStringLiteral("@alice:matrix.org"));
|
|
||||||
|
|
||||||
QCOMPARE(eventHandler.getNumberExcessReadMarkers(), QString());
|
|
||||||
QCOMPARE(eventHandler.getReadMarkersString(), QStringLiteral("1 user: @alice:matrix.org"));
|
|
||||||
|
|
||||||
EventHandler eventHandler2(room, room->messageEvents().at(2).get());
|
|
||||||
QCOMPARE(eventHandler2.hasReadMarkers(), true);
|
|
||||||
|
|
||||||
readMarkers = eventHandler2.getReadMarkers();
|
|
||||||
|
|
||||||
QCOMPARE(readMarkers.size(), 5);
|
|
||||||
|
|
||||||
QCOMPARE(eventHandler2.getNumberExcessReadMarkers(), QStringLiteral("+ 1"));
|
|
||||||
// There are no guarantees on the order of the users it will be different every time so don't match the whole string.
|
|
||||||
QCOMPARE(eventHandler2.getReadMarkersString().startsWith(QStringLiteral("6 users:")), true);
|
|
||||||
}
|
|
||||||
|
|
||||||
void EventHandlerTest::nullReadMarkers()
|
|
||||||
{
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "hasReadMarkers called with m_room set to nullptr.");
|
|
||||||
QCOMPARE(emptyHandler.hasReadMarkers(), false);
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_room set to nullptr.");
|
|
||||||
QCOMPARE(emptyHandler.getReadMarkers(), QVariantList());
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_room set to nullptr.");
|
|
||||||
QCOMPARE(emptyHandler.getNumberExcessReadMarkers(), QString());
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getReadMarkersString called with m_room set to nullptr.");
|
|
||||||
QCOMPARE(emptyHandler.getReadMarkersString(), QString());
|
|
||||||
|
|
||||||
EventHandler noEventHandler(room, nullptr);
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "hasReadMarkers called with m_event set to nullptr.");
|
|
||||||
QCOMPARE(noEventHandler.hasReadMarkers(), false);
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getReadMarkers called with m_event set to nullptr.");
|
|
||||||
QCOMPARE(noEventHandler.getReadMarkers(), QVariantList());
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getNumberExcessReadMarkers called with m_event set to nullptr.");
|
|
||||||
QCOMPARE(noEventHandler.getNumberExcessReadMarkers(), QString());
|
|
||||||
|
|
||||||
QTest::ignoreMessage(QtWarningMsg, "getReadMarkersString called with m_event set to nullptr.");
|
|
||||||
QCOMPARE(noEventHandler.getReadMarkersString(), QString());
|
|
||||||
}
|
|
||||||
|
|
||||||
QTEST_MAIN(EventHandlerTest)
|
QTEST_MAIN(EventHandlerTest)
|
||||||
#include "eventhandlertest.moc"
|
#include "eventhandlertest.moc"
|
||||||
|
|||||||
@@ -52,10 +52,8 @@ void ReactionModelTest::basicReaction()
|
|||||||
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QStringLiteral("<span style=\"font-family: 'emoji';\">👍</span>"));
|
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::ReactionRole), QStringLiteral("👍"));
|
||||||
QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole),
|
QCOMPARE(model.data(model.index(0), ReactionModel::ToolTipRole),
|
||||||
QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
QStringLiteral("Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
||||||
auto authorList = QVariantList{room->getUser(QStringLiteral("@alice:matrix.org"))};
|
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalMember), false);
|
||||||
QCOMPARE(model.data(model.index(0), ReactionModel::AuthorsRole), authorList);
|
|
||||||
QCOMPARE(model.data(model.index(0), ReactionModel::HasLocalUser), false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ReactionModelTest::newReaction()
|
void ReactionModelTest::newReaction()
|
||||||
@@ -65,7 +63,7 @@ void ReactionModelTest::newReaction()
|
|||||||
|
|
||||||
QCOMPARE(model->rowCount(), 1);
|
QCOMPARE(model->rowCount(), 1);
|
||||||
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
|
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
|
||||||
QStringLiteral("@alice:matrix.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
QStringLiteral("Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
||||||
|
|
||||||
QSignalSpy spy(model, SIGNAL(modelReset()));
|
QSignalSpy spy(model, SIGNAL(modelReset()));
|
||||||
|
|
||||||
@@ -74,7 +72,7 @@ void ReactionModelTest::newReaction()
|
|||||||
QCOMPARE(spy.count(), 2); // Once for each of the 2 new reactions.
|
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(1), ReactionModel::ReactionRole), QStringLiteral("😆"));
|
||||||
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
|
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole),
|
||||||
QStringLiteral("@alice:matrix.org and @bob:example.org reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
QStringLiteral("Alice Margatroid and Bob reacted with <span style=\"font-family: 'emoji';\">👍</span>"));
|
||||||
|
|
||||||
delete model;
|
delete model;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,6 +95,7 @@
|
|||||||
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
|
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
|
||||||
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
|
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
|
||||||
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
|
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
|
||||||
|
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
|
||||||
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
|
||||||
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
|
||||||
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
|
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
|
||||||
@@ -328,6 +329,7 @@
|
|||||||
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
|
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
|
||||||
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
|
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
|
||||||
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
|
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
|
||||||
|
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
|
||||||
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
|
||||||
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
|
||||||
<caption xml:lang="sv">Upptäck nya gemenskaper med Matrix Spaces</caption>
|
<caption xml:lang="sv">Upptäck nya gemenskaper med Matrix Spaces</caption>
|
||||||
@@ -413,6 +415,7 @@
|
|||||||
<content_attribute id="social-chat">intense</content_attribute>
|
<content_attribute id="social-chat">intense</content_attribute>
|
||||||
</content_rating>
|
</content_rating>
|
||||||
<releases>
|
<releases>
|
||||||
|
<release version="24.05.2" date="2024-07-04"/>
|
||||||
<release version="24.05.1" date="2024-06-13"/>
|
<release version="24.05.1" date="2024-06-13"/>
|
||||||
<release version="24.05.0" date="2024-05-23"/>
|
<release version="24.05.0" date="2024-05-23"/>
|
||||||
<release version="24.02.2" date="2024-04-11"/>
|
<release version="24.02.2" date="2024-04-11"/>
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ Name[eu]=NeoChat
|
|||||||
Name[fi]=NeoChat
|
Name[fi]=NeoChat
|
||||||
Name[fr]=NeoChat
|
Name[fr]=NeoChat
|
||||||
Name[gl]=NeoChat
|
Name[gl]=NeoChat
|
||||||
|
Name[he]=NeoChat
|
||||||
Name[hu]=NeoChat
|
Name[hu]=NeoChat
|
||||||
Name[ia]=Neochat
|
Name[ia]=Neochat
|
||||||
Name[id]=NeoChat
|
Name[id]=NeoChat
|
||||||
@@ -59,6 +60,7 @@ GenericName[eu]=Matrix bezeroa
|
|||||||
GenericName[fi]=Matrix-asiakas
|
GenericName[fi]=Matrix-asiakas
|
||||||
GenericName[fr]=Client « Matrix »
|
GenericName[fr]=Client « Matrix »
|
||||||
GenericName[gl]=Cliente de Matrix
|
GenericName[gl]=Cliente de Matrix
|
||||||
|
GenericName[he]=לקוח Matrix
|
||||||
GenericName[hu]=Matrix kliens
|
GenericName[hu]=Matrix kliens
|
||||||
GenericName[ia]=Cliente de Matrice
|
GenericName[ia]=Cliente de Matrice
|
||||||
GenericName[id]=Klien Matrix
|
GenericName[id]=Klien Matrix
|
||||||
@@ -99,6 +101,7 @@ Comment[eu]=Matrix protokolorako bezeroa
|
|||||||
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||||
Comment[fr]=Client pour le protocole « Matrix »
|
Comment[fr]=Client pour le protocole « Matrix »
|
||||||
Comment[gl]=Cliente para o protocolo Matrix.
|
Comment[gl]=Cliente para o protocolo Matrix.
|
||||||
|
Comment[he]=לקוח לפרוטוקול Matrix
|
||||||
Comment[hu]=Kliens a Matrix protokollhoz
|
Comment[hu]=Kliens a Matrix protokollhoz
|
||||||
Comment[ia]=Cliente per le protocollo de Matrix
|
Comment[ia]=Cliente per le protocollo de Matrix
|
||||||
Comment[id]=Klien untuk protokol Matrix
|
Comment[id]=Klien untuk protokol Matrix
|
||||||
|
|||||||
1111
po/ar/neochat.po
1111
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1150
po/az/neochat.po
1150
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -77,7 +77,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
|
|||||||
></term>
|
></term>
|
||||||
<listitem>
|
<listitem>
|
||||||
<para
|
<para
|
||||||
>L'URI de Matrix per a un usuari o una sala. P. ex. matrix:u/usuari:exemple.org o matrix:r/root:exemple.org. Això farà que el NeoChat intenti obrir la sala o conversa indicada. </para>
|
>L'URI de Matrix per a un usuari o una sala. P. ex. matrix:u/usuari:example.org o matrix:r/root:example.org. Això farà que el NeoChat intenti obrir la sala o conversa indicada. </para>
|
||||||
</listitem>
|
</listitem>
|
||||||
</varlistentry>
|
</varlistentry>
|
||||||
</variablelist>
|
</variablelist>
|
||||||
|
|||||||
1098
po/ca/neochat.po
1098
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1018
po/cs/neochat.po
1018
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1064
po/da/neochat.po
1064
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1145
po/de/neochat.po
1145
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1152
po/el/neochat.po
1152
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1125
po/en_GB/neochat.po
1125
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1036
po/eo/neochat.po
1036
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1109
po/es/neochat.po
1109
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1466
po/eu/neochat.po
1466
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1123
po/fi/neochat.po
1123
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1126
po/fr/neochat.po
1126
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
5479
po/gl/neochat.po
Normal file
5479
po/gl/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1420
po/hu/neochat.po
1420
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1107
po/ia/neochat.po
1107
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1145
po/id/neochat.po
1145
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1117
po/ie/neochat.po
1117
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1109
po/it/neochat.po
1109
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
982
po/ja/neochat.po
982
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1107
po/ka/neochat.po
1107
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1121
po/ko/neochat.po
1121
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
988
po/lt/neochat.po
988
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1122
po/lv/neochat.po
1122
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1107
po/nl/neochat.po
1107
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
2228
po/nn/neochat.po
2228
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1083
po/pa/neochat.po
1083
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1190
po/pl/neochat.po
1190
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1145
po/pt/neochat.po
1145
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1229
po/pt_BR/neochat.po
1229
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1132
po/ru/neochat.po
1132
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1105
po/sk/neochat.po
1105
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1111
po/sl/neochat.po
1111
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1109
po/sv/neochat.po
1109
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1125
po/ta/neochat.po
1125
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1046
po/tok/neochat.po
1046
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1108
po/tr/neochat.po
1108
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1113
po/uk/neochat.po
1113
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1008
po/zh_CN/neochat.po
1008
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1094
po/zh_TW/neochat.po
1094
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -134,6 +134,8 @@ add_library(neochat STATIC
|
|||||||
jobs/neochatdeletedevicejob.h
|
jobs/neochatdeletedevicejob.h
|
||||||
jobs/neochatchangepasswordjob.cpp
|
jobs/neochatchangepasswordjob.cpp
|
||||||
jobs/neochatchangepasswordjob.h
|
jobs/neochatchangepasswordjob.h
|
||||||
|
jobs/neochatgetcommonroomsjob.cpp
|
||||||
|
jobs/neochatgetcommonroomsjob.h
|
||||||
mediasizehelper.cpp
|
mediasizehelper.cpp
|
||||||
mediasizehelper.h
|
mediasizehelper.h
|
||||||
eventhandler.cpp
|
eventhandler.cpp
|
||||||
@@ -156,7 +158,6 @@ add_library(neochat STATIC
|
|||||||
models/linemodel.cpp
|
models/linemodel.cpp
|
||||||
models/linemodel.h
|
models/linemodel.h
|
||||||
events/locationbeaconevent.h
|
events/locationbeaconevent.h
|
||||||
events/serveraclevent.h
|
|
||||||
events/widgetevent.h
|
events/widgetevent.h
|
||||||
enums/messagecomponenttype.h
|
enums/messagecomponenttype.h
|
||||||
models/messagecontentmodel.cpp
|
models/messagecontentmodel.cpp
|
||||||
@@ -187,13 +188,17 @@ add_library(neochat STATIC
|
|||||||
models/permissionsmodel.h
|
models/permissionsmodel.h
|
||||||
threepidbindhelper.cpp
|
threepidbindhelper.cpp
|
||||||
threepidbindhelper.h
|
threepidbindhelper.h
|
||||||
|
models/readmarkermodel.cpp
|
||||||
|
models/readmarkermodel.h
|
||||||
|
neochatroommember.cpp
|
||||||
|
neochatroommember.h
|
||||||
)
|
)
|
||||||
|
|
||||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||||
QT_QML_SINGLETON_TYPE TRUE
|
QT_QML_SINGLETON_TYPE TRUE
|
||||||
)
|
)
|
||||||
|
|
||||||
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
|
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
|
||||||
QML_FILES
|
QML_FILES
|
||||||
qml/Main.qml
|
qml/Main.qml
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
qt_add_library(chatbar STATIC)
|
qt_add_library(chatbar STATIC)
|
||||||
qt_add_qml_module(chatbar
|
ecm_add_qml_module(chatbar GENERATE_PLUGIN_SOURCE
|
||||||
URI org.kde.neochat.chatbar
|
URI org.kde.neochat.chatbar
|
||||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/chatbar
|
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/chatbar
|
||||||
QML_FILES
|
QML_FILES
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ QQC2.Control {
|
|||||||
ReplyPane {
|
ReplyPane {
|
||||||
userName: _private.chatBarCache.relationUser.displayName
|
userName: _private.chatBarCache.relationUser.displayName
|
||||||
userColor: _private.chatBarCache.relationUser.color
|
userColor: _private.chatBarCache.relationUser.color
|
||||||
userAvatar: _private.chatBarCache.relationUser.avatarSource
|
userAvatar: _private.chatBarCache.relationUser.avatarUrl
|
||||||
text: _private.chatBarCache.relationMessage
|
text: _private.chatBarCache.relationMessage
|
||||||
|
|
||||||
onCancel: {
|
onCancel: {
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
|
|
||||||
#include "chatbarcache.h"
|
#include "chatbarcache.h"
|
||||||
|
|
||||||
|
#include <Quotient/roommember.h>
|
||||||
|
|
||||||
#include "chatdocumenthandler.h"
|
#include "chatdocumenthandler.h"
|
||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
@@ -84,7 +86,7 @@ void ChatBarCache::setEditId(const QString &editId)
|
|||||||
Q_EMIT attachmentPathChanged();
|
Q_EMIT attachmentPathChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap ChatBarCache::relationUser() const
|
Quotient::RoomMember ChatBarCache::relationUser() const
|
||||||
{
|
{
|
||||||
if (parent() == nullptr) {
|
if (parent() == nullptr) {
|
||||||
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
|
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
|
||||||
@@ -96,9 +98,9 @@ QVariantMap ChatBarCache::relationUser() const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
if (m_relationId.isEmpty()) {
|
if (m_relationId.isEmpty()) {
|
||||||
return room->getUser(QString());
|
return room->member(QString());
|
||||||
}
|
}
|
||||||
return room->getUser((*room->findInTimeline(m_relationId))->senderId());
|
return room->member((*room->findInTimeline(m_relationId))->senderId());
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ChatBarCache::relationMessage() const
|
QString ChatBarCache::relationMessage() const
|
||||||
|
|||||||
@@ -10,6 +10,12 @@
|
|||||||
|
|
||||||
class ChatDocumentHandler;
|
class ChatDocumentHandler;
|
||||||
|
|
||||||
|
namespace Quotient
|
||||||
|
{
|
||||||
|
class RoomMember;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Defines a user mention in the current chat or edit text.
|
* @brief Defines a user mention in the current chat or edit text.
|
||||||
*/
|
*/
|
||||||
@@ -88,26 +94,13 @@ class ChatBarCache : public QObject
|
|||||||
Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged)
|
Q_PROPERTY(QString editId READ editId WRITE setEditId NOTIFY relationIdChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the user for the message being replied to.
|
* @brief Get the RoomMember object for the message being replied to.
|
||||||
*
|
*
|
||||||
* This is different to getting a Quotient::User object
|
* Returns an empty RoomMember if not replying to a message.
|
||||||
* as neither of those can provide details like the displayName or avatarMediaId
|
|
||||||
* without the room context as these can vary from room to room.
|
|
||||||
*
|
*
|
||||||
* Returns an empty user if not replying to a message.
|
* @sa Quotient::RoomMember
|
||||||
*
|
|
||||||
* The user QVariantMap has the following properties:
|
|
||||||
* - isLocalUser - Whether the user is the local user.
|
|
||||||
* - id - The matrix ID of the user.
|
|
||||||
* - displayName - Display name in the context of this room.
|
|
||||||
* - avatarSource - The mxc URL for the user's avatar in the current room.
|
|
||||||
* - avatarMediaId - Avatar id in the context of this room.
|
|
||||||
* - color - Color for the user.
|
|
||||||
* - object - The Quotient::User object for the user.
|
|
||||||
*
|
|
||||||
* @sa getUser, Quotient::User
|
|
||||||
*/
|
*/
|
||||||
Q_PROPERTY(QVariantMap relationUser READ relationUser NOTIFY relationIdChanged)
|
Q_PROPERTY(Quotient::RoomMember relationUser READ relationUser NOTIFY relationIdChanged)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief The content of the related message.
|
* @brief The content of the related message.
|
||||||
@@ -161,7 +154,7 @@ public:
|
|||||||
QString editId() const;
|
QString editId() const;
|
||||||
void setEditId(const QString &editId);
|
void setEditId(const QString &editId);
|
||||||
|
|
||||||
QVariantMap relationUser() const;
|
Quotient::RoomMember relationUser() const;
|
||||||
|
|
||||||
QString relationMessage() const;
|
QString relationMessage() const;
|
||||||
|
|
||||||
|
|||||||
@@ -392,8 +392,6 @@ QString Controller::loadFileContent(const QString &path) const
|
|||||||
return QString::fromLatin1(file.readAll());
|
return QString::fromLatin1(file.readAll());
|
||||||
}
|
}
|
||||||
|
|
||||||
#include "moc_controller.cpp"
|
|
||||||
|
|
||||||
void Controller::setTestMode(bool test)
|
void Controller::setTestMode(bool test)
|
||||||
{
|
{
|
||||||
testMode = test;
|
testMode = test;
|
||||||
@@ -409,15 +407,6 @@ void Controller::removeConnection(const QString &userId)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Controller::ssssSupported() const
|
|
||||||
{
|
|
||||||
#if Quotient_VERSION_MINOR > 8 || Quotient_VERSION_PATCH > 1
|
|
||||||
return true;
|
|
||||||
#else
|
|
||||||
return false;
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Controller::csSupported() const
|
bool Controller::csSupported() const
|
||||||
{
|
{
|
||||||
#if Quotient_VERSION_MINOR > 9
|
#if Quotient_VERSION_MINOR > 9
|
||||||
@@ -426,3 +415,5 @@ bool Controller::csSupported() const
|
|||||||
return false;
|
return false;
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#include "moc_controller.cpp"
|
||||||
|
|||||||
@@ -50,10 +50,19 @@ class Controller : public QObject
|
|||||||
|
|
||||||
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
|
Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
|
||||||
|
|
||||||
Q_PROPERTY(bool ssssSupported READ ssssSupported CONSTANT)
|
|
||||||
Q_PROPERTY(bool csSupported READ csSupported CONSTANT)
|
Q_PROPERTY(bool csSupported READ csSupported CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Define the types on inline messages that can be shown.
|
||||||
|
*/
|
||||||
|
enum MessageType {
|
||||||
|
Positive, /**< Positive message, typically green. */
|
||||||
|
Info, /**< Info message, typically highlight color. */
|
||||||
|
Error, /**< Error message, typically red. */
|
||||||
|
};
|
||||||
|
Q_ENUM(MessageType)
|
||||||
|
|
||||||
static Controller &instance();
|
static Controller &instance();
|
||||||
static Controller *create(QQmlEngine *engine, QJSEngine *)
|
static Controller *create(QQmlEngine *engine, QJSEngine *)
|
||||||
{
|
{
|
||||||
@@ -97,7 +106,6 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE void removeConnection(const QString &userId);
|
Q_INVOKABLE void removeConnection(const QString &userId);
|
||||||
|
|
||||||
bool ssssSupported() const;
|
|
||||||
bool csSupported() const;
|
bool csSupported() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -127,4 +135,5 @@ Q_SIGNALS:
|
|||||||
void connectionDropped(NeoChatConnection *connection);
|
void connectionDropped(NeoChatConnection *connection);
|
||||||
void activeConnectionChanged(NeoChatConnection *connection);
|
void activeConnectionChanged(NeoChatConnection *connection);
|
||||||
void accountsLoadingChanged();
|
void accountsLoadingChanged();
|
||||||
|
void showMessage(MessageType messageType, const QString &message);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
|
||||||
|
|
||||||
#if Quotient_VERSION_MINOR > 8
|
|
||||||
#define Omittable std::optional
|
|
||||||
#define quotientNone std::nullopt
|
|
||||||
#else
|
|
||||||
#include <Quotient/omittable.h>
|
|
||||||
#define Omittable Quotient::Omittable
|
|
||||||
#define quotientNone Quotient::none
|
|
||||||
#endif
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
qt_add_library(devtools STATIC)
|
qt_add_library(devtools STATIC)
|
||||||
qt_add_qml_module(devtools
|
ecm_add_qml_module(devtools GENERATE_PLUGIN_SOURCE
|
||||||
URI org.kde.neochat.devtools
|
URI org.kde.neochat.devtools
|
||||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools
|
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools
|
||||||
QML_FILES
|
QML_FILES
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ public:
|
|||||||
* a room message.
|
* a room message.
|
||||||
*/
|
*/
|
||||||
enum Type {
|
enum Type {
|
||||||
|
Author, /**< The message sender and time. */
|
||||||
Text, /**< A text message. */
|
Text, /**< A text message. */
|
||||||
Image, /**< A message that is an image. */
|
Image, /**< A message that is an image. */
|
||||||
Audio, /**< A message that is an audio recording. */
|
Audio, /**< A message that is an audio recording. */
|
||||||
|
|||||||
@@ -19,11 +19,11 @@
|
|||||||
#include <Quotient/events/simplestateevents.h>
|
#include <Quotient/events/simplestateevents.h>
|
||||||
#include <Quotient/events/stickerevent.h>
|
#include <Quotient/events/stickerevent.h>
|
||||||
#include <Quotient/quotient_common.h>
|
#include <Quotient/quotient_common.h>
|
||||||
|
#include <Quotient/roommember.h>
|
||||||
|
|
||||||
#include "eventhandler_logging.h"
|
#include "eventhandler_logging.h"
|
||||||
#include "events/locationbeaconevent.h"
|
#include "events/locationbeaconevent.h"
|
||||||
#include "events/pollevent.h"
|
#include "events/pollevent.h"
|
||||||
#include "events/serveraclevent.h"
|
|
||||||
#include "events/widgetevent.h"
|
#include "events/widgetevent.h"
|
||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
#include "messagecomponenttype.h"
|
#include "messagecomponenttype.h"
|
||||||
@@ -61,22 +61,6 @@ MessageComponentType::Type EventHandler::messageComponentType() const
|
|||||||
return MessageComponentType::typeForEvent(*m_event);
|
return MessageComponentType::typeForEvent(*m_event);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap EventHandler::getAuthor(bool isPending) const
|
|
||||||
{
|
|
||||||
if (m_room == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr.";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
// If we have a room we can return an empty user by handing nullptr to m_room->getUser.
|
|
||||||
if (m_event == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getAuthor called with m_event set to nullptr. Returning empty user.";
|
|
||||||
return m_room->getUser(QString());
|
|
||||||
}
|
|
||||||
|
|
||||||
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
|
|
||||||
return m_room->getUser(author);
|
|
||||||
}
|
|
||||||
|
|
||||||
QString EventHandler::getAuthorDisplayName(bool isPending) const
|
QString EventHandler::getAuthorDisplayName(bool isPending) const
|
||||||
{
|
{
|
||||||
if (m_room == nullptr) {
|
if (m_room == nullptr) {
|
||||||
@@ -159,6 +143,11 @@ QString EventHandler::getTimeString(bool relative, QLocale::FormatType format, b
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QString EventHandler::getTimeString(const QString &format, bool isPending, const QDateTime &lastUpdated)
|
||||||
|
{
|
||||||
|
return getTime(isPending, lastUpdated).toLocalTime().toString(format);
|
||||||
|
}
|
||||||
|
|
||||||
bool EventHandler::isHighlighted()
|
bool EventHandler::isHighlighted()
|
||||||
{
|
{
|
||||||
if (m_room == nullptr) {
|
if (m_room == nullptr) {
|
||||||
@@ -243,17 +232,19 @@ Qt::TextFormat EventHandler::messageBodyInputFormat(const Quotient::RoomMessageE
|
|||||||
|
|
||||||
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||||
{
|
{
|
||||||
|
QString body;
|
||||||
|
|
||||||
if (event.hasFileContent()) {
|
if (event.hasFileContent()) {
|
||||||
auto fileCaption = event.content()->fileInfo()->originalName;
|
// if filename is given or body is equal to filename,
|
||||||
if (fileCaption.isEmpty()) {
|
// then body is a caption
|
||||||
fileCaption = event.plainBody();
|
QString filename = event.content()->fileInfo()->originalName;
|
||||||
} else if (event.content()->fileInfo()->originalName != event.plainBody()) {
|
QString body = event.plainBody();
|
||||||
fileCaption = event.plainBody() + " | "_ls + fileCaption;
|
if (filename.isEmpty() || filename == body) {
|
||||||
|
return QString();
|
||||||
}
|
}
|
||||||
return fileCaption;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString body;
|
|
||||||
if (event.hasTextContent() && event.content()) {
|
if (event.hasTextContent() && event.content()) {
|
||||||
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
body = static_cast<const EventContent::TextContent *>(event.content())->body;
|
||||||
} else {
|
} else {
|
||||||
@@ -321,12 +312,16 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
|||||||
auto subjectName = m_room->member(e.userId()).htmlSafeDisplayName();
|
auto subjectName = m_room->member(e.userId()).htmlSafeDisplayName();
|
||||||
if (e.membership() == Membership::Leave) {
|
if (e.membership() == Membership::Leave) {
|
||||||
if (e.prevContent() && e.prevContent()->displayName) {
|
if (e.prevContent() && e.prevContent()->displayName) {
|
||||||
subjectName = sanitized(*e.prevContent()->displayName).toHtmlEscaped();
|
subjectName = sanitized(*e.prevContent()->displayName);
|
||||||
|
if (prettyPrint) {
|
||||||
|
subjectName = subjectName.toHtmlEscaped();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (prettyPrint) {
|
if (prettyPrint) {
|
||||||
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\">%2</a>").arg(e.userId(), subjectName);
|
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
|
||||||
|
.arg(e.userId(), m_room->member(e.userId()).color().name(), subjectName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The below code assumes senderName output in AuthorRole
|
// The below code assumes senderName output in AuthorRole
|
||||||
@@ -440,7 +435,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
|||||||
[](const LocationBeaconEvent &e) {
|
[](const LocationBeaconEvent &e) {
|
||||||
return e.contentJson()["description"_ls].toString();
|
return e.contentJson()["description"_ls].toString();
|
||||||
},
|
},
|
||||||
[](const ServerAclEvent &) {
|
[](const RoomServerAclEvent &) {
|
||||||
return i18n("changed the server access control lists for this room");
|
return i18n("changed the server access control lists for this room");
|
||||||
},
|
},
|
||||||
[](const WidgetEvent &e) {
|
[](const WidgetEvent &e) {
|
||||||
@@ -609,7 +604,7 @@ QString EventHandler::getGenericBody() const
|
|||||||
[](const LocationBeaconEvent &) {
|
[](const LocationBeaconEvent &) {
|
||||||
return i18n("sent a live location beacon");
|
return i18n("sent a live location beacon");
|
||||||
},
|
},
|
||||||
[](const ServerAclEvent &) {
|
[](const RoomServerAclEvent &) {
|
||||||
return i18n("changed the server access control lists for this room");
|
return i18n("changed the server access control lists for this room");
|
||||||
},
|
},
|
||||||
[](const WidgetEvent &e) {
|
[](const WidgetEvent &e) {
|
||||||
@@ -658,23 +653,30 @@ QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event)
|
|||||||
QString eventId = event->id();
|
QString eventId = event->id();
|
||||||
|
|
||||||
// Get the file info for the event.
|
// Get the file info for the event.
|
||||||
const EventContent::FileInfo *fileInfo;
|
|
||||||
bool isSticker = false;
|
|
||||||
if (event->is<RoomMessageEvent>()) {
|
if (event->is<RoomMessageEvent>()) {
|
||||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
|
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
|
||||||
if (!roomMessageEvent->hasFileContent()) {
|
if (!roomMessageEvent->hasFileContent()) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const EventContent::FileInfo *fileInfo;
|
||||||
fileInfo = roomMessageEvent->content()->fileInfo();
|
fileInfo = roomMessageEvent->content()->fileInfo();
|
||||||
|
QVariantMap mediaInfo = getMediaInfoFromFileInfo(fileInfo, eventId, false, false);
|
||||||
|
// if filename isn't specifically given, it is in body
|
||||||
|
// https://spec.matrix.org/latest/client-server-api/#mfile
|
||||||
|
mediaInfo["filename"_ls] = (fileInfo->originalName.isEmpty()) ? roomMessageEvent->plainBody() : fileInfo->originalName;
|
||||||
|
|
||||||
|
return mediaInfo;
|
||||||
} else if (event->is<StickerEvent>()) {
|
} else if (event->is<StickerEvent>()) {
|
||||||
|
const EventContent::FileInfo *fileInfo;
|
||||||
|
|
||||||
auto stickerEvent = eventCast<const StickerEvent>(event);
|
auto stickerEvent = eventCast<const StickerEvent>(event);
|
||||||
fileInfo = &stickerEvent->image();
|
fileInfo = &stickerEvent->image();
|
||||||
isSticker = true;
|
|
||||||
|
return getMediaInfoFromFileInfo(fileInfo, eventId, false, true);
|
||||||
} else {
|
} else {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
return getMediaInfoFromFileInfo(fileInfo, eventId, false, isSticker);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail, bool isSticker) const
|
QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail, bool isSticker) const
|
||||||
@@ -800,24 +802,21 @@ MessageComponentType::Type EventHandler::replyMessageComponentType() const
|
|||||||
return MessageComponentType::typeForEvent(*replyEvent);
|
return MessageComponentType::typeForEvent(*replyEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantMap EventHandler::getReplyAuthor() const
|
Quotient::RoomMember EventHandler::getReplyAuthor() const
|
||||||
{
|
{
|
||||||
if (m_room == nullptr) {
|
if (m_room == nullptr) {
|
||||||
qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr.";
|
qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr.";
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
// If we have a room we can return an empty user by handing nullptr to m_room->getUser.
|
|
||||||
if (m_event == nullptr) {
|
if (m_event == nullptr) {
|
||||||
qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user.";
|
qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user.";
|
||||||
return m_room->getUser(QString());
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
auto replyPtr = m_room->getReplyForEvent(*m_event);
|
if (auto replyPtr = m_room->getReplyForEvent(*m_event)) {
|
||||||
|
return m_room->member(replyPtr->senderId());
|
||||||
if (replyPtr) {
|
|
||||||
return m_room->getUser(replyPtr->senderId());
|
|
||||||
} else {
|
} else {
|
||||||
return m_room->getUser(QString());
|
return m_room->member(QString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -953,101 +952,4 @@ QString EventHandler::getLocationAssetType() const
|
|||||||
return assetType;
|
return assetType;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool EventHandler::hasReadMarkers() const
|
|
||||||
{
|
|
||||||
if (m_room == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "hasReadMarkers called with m_room set to nullptr.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (m_event == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "hasReadMarkers called with m_event set to nullptr.";
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto userIds = m_room->userIdsAtEvent(m_event->id());
|
|
||||||
userIds.remove(m_room->localMember().id());
|
|
||||||
return userIds.size() > 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList EventHandler::getReadMarkers(int maxMarkers) const
|
|
||||||
{
|
|
||||||
if (m_room == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getReadMarkers called with m_room set to nullptr.";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (m_event == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getReadMarkers called with m_event set to nullptr.";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto userIds_temp = m_room->userIdsAtEvent(m_event->id());
|
|
||||||
userIds_temp.remove(m_room->localMember().id());
|
|
||||||
|
|
||||||
auto userIds = userIds_temp.values();
|
|
||||||
if (userIds.count() > maxMarkers) {
|
|
||||||
userIds = userIds.mid(0, maxMarkers);
|
|
||||||
}
|
|
||||||
|
|
||||||
QVariantList users;
|
|
||||||
users.reserve(userIds.size());
|
|
||||||
for (const auto &userId : userIds) {
|
|
||||||
auto user = m_room->member(userId);
|
|
||||||
users += m_room->getUser(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
return users;
|
|
||||||
}
|
|
||||||
|
|
||||||
QString EventHandler::getNumberExcessReadMarkers(int maxMarkers) const
|
|
||||||
{
|
|
||||||
if (m_room == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getNumberExcessReadMarkers called with m_room set to nullptr.";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (m_event == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getNumberExcessReadMarkers called with m_event set to nullptr.";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto userIds = m_room->userIdsAtEvent(m_event->id());
|
|
||||||
userIds.remove(m_room->localMember().id());
|
|
||||||
|
|
||||||
if (userIds.count() > maxMarkers) {
|
|
||||||
return QStringLiteral("+ ") + QString::number(userIds.count() - maxMarkers);
|
|
||||||
} else {
|
|
||||||
return QString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
QString EventHandler::getReadMarkersString() const
|
|
||||||
{
|
|
||||||
if (m_room == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getReadMarkersString called with m_room set to nullptr.";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
if (m_event == nullptr) {
|
|
||||||
qCWarning(EventHandling) << "getReadMarkersString called with m_event set to nullptr.";
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
auto userIds = m_room->userIdsAtEvent(m_event->id());
|
|
||||||
userIds.remove(m_room->localMember().id());
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The string ends up in the form
|
|
||||||
* "x users: user1DisplayName, user2DisplayName, etc."
|
|
||||||
*/
|
|
||||||
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
|
|
||||||
for (const auto &userId : userIds) {
|
|
||||||
auto user = m_room->member(userId);
|
|
||||||
auto displayName = user.displayName();
|
|
||||||
if (displayName.isEmpty()) {
|
|
||||||
displayName = userId;
|
|
||||||
}
|
|
||||||
readMarkersString += displayName + i18nc("list separator", ", ");
|
|
||||||
}
|
|
||||||
readMarkersString.chop(2);
|
|
||||||
return readMarkersString;
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "moc_eventhandler.cpp"
|
#include "moc_eventhandler.cpp"
|
||||||
|
|||||||
@@ -13,6 +13,11 @@
|
|||||||
|
|
||||||
#include "enums/messagecomponenttype.h"
|
#include "enums/messagecomponenttype.h"
|
||||||
|
|
||||||
|
namespace Quotient
|
||||||
|
{
|
||||||
|
class RoomMember;
|
||||||
|
}
|
||||||
|
|
||||||
class LinkPreviewer;
|
class LinkPreviewer;
|
||||||
class NeoChatRoom;
|
class NeoChatRoom;
|
||||||
class ReactionModel;
|
class ReactionModel;
|
||||||
@@ -48,38 +53,10 @@ public:
|
|||||||
*/
|
*/
|
||||||
MessageComponentType::Type messageComponentType() const;
|
MessageComponentType::Type messageComponentType() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Get the author of the event in context of the room.
|
|
||||||
*
|
|
||||||
* This is different to getting a Quotient::User object
|
|
||||||
* as neither of those can provide details like the displayName or avatarMediaId
|
|
||||||
* without the room context as these can vary from room to room. This function
|
|
||||||
* uses the room context and outputs the result as QVariantMap.
|
|
||||||
*
|
|
||||||
* An empty QVariantMap will be returned if the EventHandler hasn't had the room
|
|
||||||
* intialised. An empty user (i.e. a QVariantMap with all the correct keys
|
|
||||||
* but empty values) will be returned if the room has been set but not an event.
|
|
||||||
*
|
|
||||||
* @param isPending if the event is pending, i.e. has not been confirmed by
|
|
||||||
* the server.
|
|
||||||
*
|
|
||||||
* @return a QVariantMap for the user with the following properties:
|
|
||||||
* - isLocalUser - Whether the user is the local user.
|
|
||||||
* - id - The matrix ID of the user.
|
|
||||||
* - displayName - Display name in the context of this room.
|
|
||||||
* - avatarSource - The mxc URL for the user's avatar in the current room.
|
|
||||||
* - avatarMediaId - Avatar id in the context of this room.
|
|
||||||
* - color - Color for the user.
|
|
||||||
* - object - The Quotient::User object for the user.
|
|
||||||
*
|
|
||||||
* @sa Quotient::User
|
|
||||||
*/
|
|
||||||
QVariantMap getAuthor(bool isPending = false) const;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the display name of the event author.
|
* @brief Get the display name of the event author.
|
||||||
*
|
*
|
||||||
* This method is separate from getAuthor() and special in that it will return
|
* This method is special in that it will return
|
||||||
* the old display name of the author if the current event is one that caused it
|
* the old display name of the author if the current event is one that caused it
|
||||||
* to change. This allows for scenarios where the UI wishes to notify that a
|
* to change. This allows for scenarios where the UI wishes to notify that a
|
||||||
* user's display name has changed and what it changed from.
|
* user's display name has changed and what it changed from.
|
||||||
@@ -121,6 +98,8 @@ public:
|
|||||||
*/
|
*/
|
||||||
QString getTimeString(bool relative, QLocale::FormatType format = QLocale::ShortFormat, bool isPending = false, QDateTime lastUpdated = {}) const;
|
QString getTimeString(bool relative, QLocale::FormatType format = QLocale::ShortFormat, bool isPending = false, QDateTime lastUpdated = {}) const;
|
||||||
|
|
||||||
|
QString getTimeString(const QString &format, bool isPending = false, const QDateTime &lastUpdated = {});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Whether the event should be highlighted in the timeline.
|
* @brief Whether the event should be highlighted in the timeline.
|
||||||
*
|
*
|
||||||
@@ -251,27 +230,17 @@ public:
|
|||||||
/**
|
/**
|
||||||
* @brief Get the author of the event replied to in context of the room.
|
* @brief Get the author of the event replied to in context of the room.
|
||||||
*
|
*
|
||||||
* This is different to getting a Quotient::User object
|
* An empty Quotient::RoomMember will be returned if the EventHandler hasn't had
|
||||||
* as neither of those can provide details like the displayName or avatarMediaId
|
* the room or event initialised.
|
||||||
* without the room context as these can vary from room to room. This function
|
|
||||||
* uses the room context and outputs the result as QVariantMap.
|
|
||||||
*
|
*
|
||||||
* An empty QVariantMap will be returned if the EventHandler hasn't had the room
|
* @param isPending if the event is pending, i.e. has not been confirmed by
|
||||||
* intialised. An empty user (i.e. a QVariantMap with all the correct keys
|
* the server.
|
||||||
* but empty values) will be returned if the room has been set but not an event.
|
|
||||||
*
|
*
|
||||||
* @return a QVariantMap for the user with the following properties:
|
* @return a Quotient::RoomMember object for the user.
|
||||||
* - isLocalUser - Whether the user is the local user.
|
|
||||||
* - id - The matrix ID of the user.
|
|
||||||
* - displayName - Display name in the context of this room.
|
|
||||||
* - avatarSource - The mxc URL for the user's avatar in the current room.
|
|
||||||
* - avatarMediaId - Avatar id in the context of this room.
|
|
||||||
* - color - Color for the user.
|
|
||||||
* - object - The Quotient::User object for the user.
|
|
||||||
*
|
*
|
||||||
* @sa Quotient::User
|
* @sa Quotient::RoomMember
|
||||||
*/
|
*/
|
||||||
QVariantMap getReplyAuthor() const;
|
Quotient::RoomMember getReplyAuthor() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Output a string for the message content of the event replied to ready
|
* @brief Output a string for the message content of the event replied to ready
|
||||||
@@ -360,43 +329,6 @@ public:
|
|||||||
*/
|
*/
|
||||||
QString getLocationAssetType() const;
|
QString getLocationAssetType() const;
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Whether the event has any read marker for other users.
|
|
||||||
*/
|
|
||||||
bool hasReadMarkers() const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a list of user read marker for the event.
|
|
||||||
*
|
|
||||||
* @param maxMarkers the maximum number of users to return. Usually the number
|
|
||||||
* of user read makers shown is limited to not clutter the UI.
|
|
||||||
* This needs to be the same as used in getNumberExcessReadMarkers
|
|
||||||
* so that the markers line up with the number displayed, i.e.
|
|
||||||
* the number of users shown plus the excess number will be
|
|
||||||
* the total number of other user read markers at an event.
|
|
||||||
*/
|
|
||||||
QVariantList getReadMarkers(int maxMarkers = 5) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the number of excess user read markers for the event.
|
|
||||||
*
|
|
||||||
* This returns a string in the form "+ x" ready for use in the UI.
|
|
||||||
*
|
|
||||||
* @param maxMarkers the maximum number of markers shown in the UI. This needs to
|
|
||||||
* be the same as used in getReadMarkers so that the value lines
|
|
||||||
* up with the number displayed, i.e. the number of users shown
|
|
||||||
* plus the excess number will be the total number of other user
|
|
||||||
* read markers at an event.
|
|
||||||
*/
|
|
||||||
QString getNumberExcessReadMarkers(int maxMarkers = 5) const;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns a string with the names of the read markers at the event.
|
|
||||||
*
|
|
||||||
* This is in the form "x users: name 1, name 2, ...".
|
|
||||||
*/
|
|
||||||
QString getReadMarkersString() const;
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
const NeoChatRoom *m_room = nullptr;
|
const NeoChatRoom *m_room = nullptr;
|
||||||
const Quotient::RoomEvent *m_event = nullptr;
|
const Quotient::RoomEvent *m_event = nullptr;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "imagepackevent.h"
|
#include "imagepackevent.h"
|
||||||
#include <QJsonObject>
|
#include <QJsonObject>
|
||||||
|
#include <Quotient/omittable.h>
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
@@ -16,16 +17,16 @@ ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
|||||||
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["attribution"_ls]),
|
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["attribution"_ls]),
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
pack = quotientNone;
|
pack = std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto &keys = json["images"_ls].toObject().keys();
|
const auto &keys = json["images"_ls].toObject().keys();
|
||||||
for (const auto &k : keys) {
|
for (const auto &k : keys) {
|
||||||
Omittable<EventContent::ImageInfo> info;
|
std::optional<EventContent::ImageInfo> info;
|
||||||
if (json["images"_ls][k].toObject().contains(QStringLiteral("info"))) {
|
if (json["images"_ls][k].toObject().contains(QStringLiteral("info"))) {
|
||||||
info = EventContent::ImageInfo(QUrl(json["images"_ls][k]["url"_ls].toString()), json["images"_ls][k]["info"_ls].toObject(), k);
|
info = EventContent::ImageInfo(QUrl(json["images"_ls][k]["url"_ls].toString()), json["images"_ls][k]["info"_ls].toObject(), k);
|
||||||
} else {
|
} else {
|
||||||
info = quotientNone;
|
info = std::nullopt;
|
||||||
}
|
}
|
||||||
images += ImagePackImage{
|
images += ImagePackImage{
|
||||||
k,
|
k,
|
||||||
|
|||||||
@@ -7,8 +7,6 @@
|
|||||||
#include <Quotient/events/eventcontent.h>
|
#include <Quotient/events/eventcontent.h>
|
||||||
#include <Quotient/events/stateevent.h>
|
#include <Quotient/events/stateevent.h>
|
||||||
|
|
||||||
#include "definitions.h"
|
|
||||||
|
|
||||||
namespace Quotient
|
namespace Quotient
|
||||||
{
|
{
|
||||||
/**
|
/**
|
||||||
@@ -28,10 +26,10 @@ public:
|
|||||||
* @brief Defines the properties of an image pack.
|
* @brief Defines the properties of an image pack.
|
||||||
*/
|
*/
|
||||||
struct Pack {
|
struct Pack {
|
||||||
Omittable<QString> displayName; /**< The display name of the pack. */
|
std::optional<QString> displayName; /**< The display name of the pack. */
|
||||||
Omittable<QUrl> avatarUrl; /**< The source mxc URL for the pack avatar. */
|
std::optional<QUrl> avatarUrl; /**< The source mxc URL for the pack avatar. */
|
||||||
Omittable<QStringList> usage; /**< An array of the usages for this pack. Possible usages are "emoticon" and "sticker". */
|
std::optional<QStringList> usage; /**< An array of the usages for this pack. Possible usages are "emoticon" and "sticker". */
|
||||||
Omittable<QString> attribution; /**< The attribution for the pack author(s). */
|
std::optional<QString> attribution; /**< The attribution for the pack author(s). */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,14 +38,14 @@ public:
|
|||||||
struct ImagePackImage {
|
struct ImagePackImage {
|
||||||
QString shortcode; /**< The shortcode for the image. */
|
QString shortcode; /**< The shortcode for the image. */
|
||||||
QUrl url; /**< The mxc URL for this image. */
|
QUrl url; /**< The mxc URL for this image. */
|
||||||
Omittable<QString> body; /**< An optional text body for this image. */
|
std::optional<QString> body; /**< An optional text body for this image. */
|
||||||
Omittable<Quotient::EventContent::ImageInfo> info; /**< The ImageInfo object used for the info block of m.sticker events. */
|
std::optional<Quotient::EventContent::ImageInfo> info; /**< The ImageInfo object used for the info block of m.sticker events. */
|
||||||
/**
|
/**
|
||||||
* @brief An array of the usages for this image.
|
* @brief An array of the usages for this image.
|
||||||
*
|
*
|
||||||
* The possible values match those of the usage key of a pack object.
|
* The possible values match those of the usage key of a pack object.
|
||||||
*/
|
*/
|
||||||
Omittable<QStringList> usage;
|
std::optional<QStringList> usage;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -55,7 +53,7 @@ public:
|
|||||||
*
|
*
|
||||||
* @sa Pack
|
* @sa Pack
|
||||||
*/
|
*/
|
||||||
Omittable<Pack> pack;
|
std::optional<Pack> pack;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Return a vector of images in the pack.
|
* @brief Return a vector of images in the pack.
|
||||||
|
|||||||
@@ -1,14 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Quotient/events/simplestateevents.h>
|
|
||||||
|
|
||||||
namespace Quotient
|
|
||||||
{
|
|
||||||
|
|
||||||
// Defined so we can directly switch on type.
|
|
||||||
DEFINE_SIMPLE_STATE_EVENT(ServerAclEvent, "m.room.server_acl", bool, allow_ip_literals, "allow_ip_literals")
|
|
||||||
|
|
||||||
} // namespace Quotient
|
|
||||||
@@ -6,10 +6,9 @@
|
|||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
#include <Quotient/accountregistry.h>
|
#include <Quotient/accountregistry.h>
|
||||||
#include <Quotient/keyverificationsession.h>
|
|
||||||
#if Quotient_VERSION_MINOR > 8 || Quotient_VERSION_PATCH > 1
|
|
||||||
#include <Quotient/e2ee/sssshandler.h>
|
#include <Quotient/e2ee/sssshandler.h>
|
||||||
#endif
|
#include <Quotient/keyverificationsession.h>
|
||||||
|
#include <Quotient/roommember.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
@@ -47,10 +46,8 @@ struct ForeignKeyVerificationSession {
|
|||||||
QML_UNCREATABLE("")
|
QML_UNCREATABLE("")
|
||||||
};
|
};
|
||||||
|
|
||||||
#if Quotient_VERSION_MINOR > 8 || Quotient_VERSION_PATCH > 1
|
|
||||||
struct ForeignSSSSHandler {
|
struct ForeignSSSSHandler {
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
QML_FOREIGN(Quotient::SSSSHandler)
|
QML_FOREIGN(Quotient::SSSSHandler)
|
||||||
QML_NAMED_ELEMENT(SSSSHandler)
|
QML_NAMED_ELEMENT(SSSSHandler)
|
||||||
};
|
};
|
||||||
#endif
|
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/omittable.h>
|
#include <Quotient/omittable.h>
|
||||||
|
|
||||||
#include "definitions.h"
|
|
||||||
|
|
||||||
class NeochatAdd3PIdJob : public Quotient::BaseJob
|
class NeochatAdd3PIdJob : public Quotient::BaseJob
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Omittable<QJsonObject> &auth = {});
|
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,12 +4,11 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/omittable.h>
|
|
||||||
|
|
||||||
#include "definitions.h"
|
#include <Quotient/omittable.h>
|
||||||
|
|
||||||
class NeochatChangePasswordJob : public Quotient::BaseJob
|
class NeochatChangePasswordJob : public Quotient::BaseJob
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Omittable<QJsonObject> &auth = {});
|
explicit NeochatChangePasswordJob(const QString &newPassword, bool logoutDevices, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/omittable.h>
|
#include <Quotient/omittable.h>
|
||||||
|
|
||||||
#include "definitions.h"
|
|
||||||
|
|
||||||
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
|
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit NeoChatDeactivateAccountJob(const Omittable<QJsonObject> &auth = {});
|
explicit NeoChatDeactivateAccountJob(const Quotient::Omittable<QJsonObject> &auth = {});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,10 +6,8 @@
|
|||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/omittable.h>
|
#include <Quotient/omittable.h>
|
||||||
|
|
||||||
#include "definitions.h"
|
|
||||||
|
|
||||||
class NeochatDeleteDeviceJob : public Quotient::BaseJob
|
class NeochatDeleteDeviceJob : public Quotient::BaseJob
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
explicit NeochatDeleteDeviceJob(const QString &deviceId, const Omittable<QJsonObject> &auth = {});
|
explicit NeochatDeleteDeviceJob(const QString &deviceId, const Quotient::Omittable<QJsonObject> &auth = {});
|
||||||
};
|
};
|
||||||
|
|||||||
14
src/jobs/neochatgetcommonroomsjob.cpp
Normal file
14
src/jobs/neochatgetcommonroomsjob.cpp
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "neochatgetcommonroomsjob.h"
|
||||||
|
|
||||||
|
using namespace Quotient;
|
||||||
|
|
||||||
|
NeochatGetCommonRoomsJob::NeochatGetCommonRoomsJob(const QString &userId)
|
||||||
|
: BaseJob(HttpVerb::Get,
|
||||||
|
QStringLiteral("GetCommonRoomsJob"),
|
||||||
|
QStringLiteral("/_matrix/client/unstable/uk.half-shot.msc2666/user/mutual_rooms").toLatin1(),
|
||||||
|
QUrlQuery({{QStringLiteral("user_id"), userId}}))
|
||||||
|
{
|
||||||
|
}
|
||||||
14
src/jobs/neochatgetcommonroomsjob.h
Normal file
14
src/jobs/neochatgetcommonroomsjob.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <Quotient/jobs/basejob.h>
|
||||||
|
#include <Quotient/omittable.h>
|
||||||
|
|
||||||
|
// TODO: Upstream to libQuotient
|
||||||
|
class NeochatGetCommonRoomsJob : public Quotient::BaseJob
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
explicit NeochatGetCommonRoomsJob(const QString &userId);
|
||||||
|
};
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
# SPDX-License-Identifier: BSD-2-Clause
|
# SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
||||||
qt_add_library(login STATIC)
|
qt_add_library(login STATIC)
|
||||||
qt_add_qml_module(login
|
ecm_add_qml_module(login GENERATE_PLUGIN_SOURCE
|
||||||
URI org.kde.neochat.login
|
URI org.kde.neochat.login
|
||||||
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/login
|
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/login
|
||||||
QML_FILES
|
QML_FILES
|
||||||
|
|||||||
@@ -13,6 +13,7 @@
|
|||||||
#include <QQuickStyle>
|
#include <QQuickStyle>
|
||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
#include <QtQml/QQmlExtensionPlugin>
|
#include <QtQml/QQmlExtensionPlugin>
|
||||||
|
#include <Quotient/connection.h>
|
||||||
|
|
||||||
#ifdef Q_OS_ANDROID
|
#ifdef Q_OS_ANDROID
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
@@ -174,6 +175,7 @@ int main(int argc, char *argv[])
|
|||||||
initLogging();
|
initLogging();
|
||||||
|
|
||||||
Connection::setEncryptionDefault(true);
|
Connection::setEncryptionDefault(true);
|
||||||
|
Connection::setDirectChatEncryptionDefault(true);
|
||||||
|
|
||||||
#ifdef NEOCHAT_FLATPAK
|
#ifdef NEOCHAT_FLATPAK
|
||||||
// Copy over the included FontConfig configuration to the
|
// Copy over the included FontConfig configuration to the
|
||||||
|
|||||||
@@ -4,11 +4,13 @@
|
|||||||
#include "actionsmodel.h"
|
#include "actionsmodel.h"
|
||||||
|
|
||||||
#include "chatbarcache.h"
|
#include "chatbarcache.h"
|
||||||
|
#include "controller.h"
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roommanager.h"
|
#include "roommanager.h"
|
||||||
#include <Quotient/events/roommemberevent.h>
|
#include <Quotient/events/roommemberevent.h>
|
||||||
#include <Quotient/events/roompowerlevelsevent.h>
|
#include <Quotient/events/roompowerlevelsevent.h>
|
||||||
|
#include <Quotient/user.h>
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
@@ -22,14 +24,15 @@ QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls
|
|||||||
|
|
||||||
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18n("Leaving this room."));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18n("Leaving this room."));
|
||||||
room->connection()->leaveRoom(room);
|
room->connection()->leaveRoom(room);
|
||||||
} else {
|
} else {
|
||||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||||
auto regexMatch = roomRegex.match(text);
|
auto regexMatch = roomRegex.match(text);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
Q_EMIT Controller::instance().showMessage(
|
||||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto leaving = room->connection()->room(text);
|
auto leaving = room->connection()->room(text);
|
||||||
@@ -37,10 +40,10 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
|
|||||||
leaving = room->connection()->roomByAlias(text);
|
leaving = room->connection()->roomByAlias(text);
|
||||||
}
|
}
|
||||||
if (leaving) {
|
if (leaving) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Leaving room <roomname>.", "Leaving room %1.", text));
|
||||||
room->connection()->leaveRoom(leaving);
|
room->connection()->leaveRoom(leaving);
|
||||||
} else {
|
} else {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Room <roomname> not found", "Room %1 not found.", text));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return QString();
|
return QString();
|
||||||
@@ -48,7 +51,7 @@ auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *
|
|||||||
|
|
||||||
auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
auto roomNickLambda = [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
|
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("No new nickname provided, no changes will happen."));
|
||||||
} else {
|
} else {
|
||||||
room->connection()->user()->rename(text, room);
|
room->connection()->user()->rename(text, room);
|
||||||
}
|
}
|
||||||
@@ -190,28 +193,31 @@ QList<ActionsModel::Action> actions{
|
|||||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||||
auto regexMatch = mxidRegex.match(text);
|
auto regexMatch = mxidRegex.match(text);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
|
const RoomMemberEvent *roomMemberEvent = room->currentState().get<RoomMemberEvent>(text);
|
||||||
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
|
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Invite) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||||
|
i18nc("<user> is already invited to this room.", "%1 is already invited to this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Ban) {
|
if (roomMemberEvent && roomMemberEvent->membership() == Membership::Ban) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is banned from this room.", "%1 is banned from this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (room->localMember().id() == text) {
|
if (room->localMember().id() == text) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18n("You are already in this room."));
|
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("You are already in this room."));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (room->memberIds().contains(text)) {
|
if (room->members().contains(room->member(text))) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
room->inviteToRoom(text);
|
room->inviteToRoom(text);
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was invited into this room", "%1 was invited into this room", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||||
|
i18nc("<username> was invited into this room", "%1 was invited into this room", text));
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -225,8 +231,9 @@ QList<ActionsModel::Action> actions{
|
|||||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||||
auto regexMatch = roomRegex.match(text);
|
auto regexMatch = roomRegex.match(text);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
Q_EMIT Controller::instance().showMessage(
|
||||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||||
@@ -234,7 +241,7 @@ QList<ActionsModel::Action> actions{
|
|||||||
RoomManager::instance().resolveResource(targetRoom->id());
|
RoomManager::instance().resolveResource(targetRoom->id());
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||||
RoomManager::instance().resolveResource(text, "join"_ls);
|
RoomManager::instance().resolveResource(text, "join"_ls);
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
@@ -251,8 +258,9 @@ QList<ActionsModel::Action> actions{
|
|||||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||||
auto regexMatch = roomRegex.match(roomName);
|
auto regexMatch = roomRegex.match(roomName);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
Q_EMIT Controller::instance().showMessage(
|
||||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
auto targetRoom = text.startsWith(QLatin1Char('!')) ? room->connection()->room(text) : room->connection()->roomByAlias(text);
|
||||||
@@ -260,7 +268,7 @@ QList<ActionsModel::Action> actions{
|
|||||||
RoomManager::instance().resolveResource(targetRoom->id());
|
RoomManager::instance().resolveResource(targetRoom->id());
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||||
auto connection = dynamic_cast<NeoChatConnection *>(room->connection());
|
auto connection = dynamic_cast<NeoChatConnection *>(room->connection());
|
||||||
const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
|
const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
|
||||||
if (parts.length() >= 2) {
|
if (parts.length() >= 2) {
|
||||||
@@ -281,15 +289,16 @@ QList<ActionsModel::Action> actions{
|
|||||||
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
QRegularExpression roomRegex(QStringLiteral(R"(^[#!][^:]+:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?)"));
|
||||||
auto regexMatch = roomRegex.match(text);
|
auto regexMatch = roomRegex.match(text);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
Q_EMIT Controller::instance().showMessage(
|
||||||
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a room id or alias.", "'%1' does not look like a room id or alias.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
|
if (room->connection()->room(text) || room->connection()->roomByAlias(text)) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("You are already in room <roomname>.", "You are already in room %1.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("Joining room <roomname>.", "Joining room %1.", text));
|
||||||
RoomManager::instance().resolveResource(text, "join"_ls);
|
RoomManager::instance().resolveResource(text, "join"_ls);
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
@@ -318,7 +327,7 @@ QList<ActionsModel::Action> actions{
|
|||||||
QStringLiteral("nick"),
|
QStringLiteral("nick"),
|
||||||
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
[](const QString &text, NeoChatRoom *room, ChatBarCache *) {
|
||||||
if (text.isEmpty()) {
|
if (text.isEmpty()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("No new nickname provided, no changes will happen."));
|
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("No new nickname provided, no changes will happen."));
|
||||||
} else {
|
} else {
|
||||||
room->connection()->user()->rename(text);
|
room->connection()->user()->rename(text);
|
||||||
}
|
}
|
||||||
@@ -352,15 +361,17 @@ QList<ActionsModel::Action> actions{
|
|||||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||||
auto regexMatch = mxidRegex.match(text);
|
auto regexMatch = mxidRegex.match(text);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (room->connection()->ignoredUsers().contains(text)) {
|
if (room->connection()->ignoredUsers().contains(text)) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
room->connection()->addToIgnoredUsers(text);
|
room->connection()->addToIgnoredUsers(text);
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
|
||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -375,15 +386,16 @@ QList<ActionsModel::Action> actions{
|
|||||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||||
auto regexMatch = mxidRegex.match(text);
|
auto regexMatch = mxidRegex.match(text);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (!room->connection()->ignoredUsers().contains(text)) {
|
if (!room->connection()->ignoredUsers().contains(text)) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
room->connection()->removeFromIgnoredUsers(text);
|
room->connection()->removeFromIgnoredUsers(text);
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -419,12 +431,14 @@ QList<ActionsModel::Action> actions{
|
|||||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||||
auto regexMatch = mxidRegex.match(parts[0]);
|
auto regexMatch = mxidRegex.match(parts[0]);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto state = room->currentState().get<RoomMemberEvent>(parts[0]);
|
auto state = room->currentState().get<RoomMemberEvent>(parts[0]);
|
||||||
if (state && state->membership() == Membership::Ban) {
|
if (state && state->membership() == Membership::Ban) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||||
|
i18nc("<user> is already banned from this room.", "%1 is already banned from this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
@@ -432,17 +446,18 @@ QList<ActionsModel::Action> actions{
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
|
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to ban users from this room."));
|
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You are not allowed to ban users from this room."));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) {
|
if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) {
|
||||||
Q_EMIT room->showMessage(
|
Q_EMIT Controller::instance().showMessage(
|
||||||
NeoChatRoom::Error,
|
Controller::Error,
|
||||||
i18nc("You are not allowed to ban <username> from this room.", "You are not allowed to ban %1 from this room.", parts[0]));
|
i18nc("You are not allowed to ban <username> from this room.", "You are not allowed to ban %1 from this room.", parts[0]));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
room->ban(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
|
room->ban(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was banned from this room.", "%1 was banned from this room.", parts[0]));
|
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||||
|
i18nc("<username> was banned from this room.", "%1 was banned from this room.", parts[0]));
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
@@ -457,7 +472,8 @@ QList<ActionsModel::Action> actions{
|
|||||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||||
auto regexMatch = mxidRegex.match(text);
|
auto regexMatch = mxidRegex.match(text);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||||
|
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
@@ -465,16 +481,18 @@ QList<ActionsModel::Action> actions{
|
|||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
|
if (plEvent->ban() > plEvent->powerLevelForUser(room->localMember().id())) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to unban users from this room."));
|
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You are not allowed to unban users from this room."));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto state = room->currentState().get<RoomMemberEvent>(text);
|
auto state = room->currentState().get<RoomMemberEvent>(text);
|
||||||
if (state && state->membership() != Membership::Ban) {
|
if (state && state->membership() != Membership::Ban) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Info,
|
||||||
|
i18nc("<user> is not banned from this room.", "%1 is not banned from this room.", text));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
room->unban(text);
|
room->unban(text);
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
|
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||||
|
i18nc("<username> was unbanned from this room.", "%1 was unbanned from this room.", text));
|
||||||
|
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
@@ -491,16 +509,16 @@ QList<ActionsModel::Action> actions{
|
|||||||
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
QStringLiteral(R"((^|[][[:space:](){}`'";])([!#@][-a-z0-9_=#/.]{1,252}:\w(?:\w|\.|-)*\.\w+(?::\d{1,5})?))"));
|
||||||
auto regexMatch = mxidRegex.match(parts[0]);
|
auto regexMatch = mxidRegex.match(parts[0]);
|
||||||
if (!regexMatch.hasMatch()) {
|
if (!regexMatch.hasMatch()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error,
|
Q_EMIT Controller::instance().showMessage(Controller::Error,
|
||||||
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0]));
|
i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", parts[0]));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (parts[0] == room->localMember().id()) {
|
if (parts[0] == room->localMember().id()) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You cannot kick yourself from the room."));
|
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You cannot kick yourself from the room."));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (!room->isMember(parts[0])) {
|
if (!room->isMember(parts[0])) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
|
Q_EMIT Controller::instance().showMessage(Controller::Error, i18nc("<username> is not in this room", "%1 is not in this room.", parts[0]));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
auto plEvent = room->currentState().get<RoomPowerLevelsEvent>();
|
||||||
@@ -509,17 +527,18 @@ QList<ActionsModel::Action> actions{
|
|||||||
}
|
}
|
||||||
auto kick = plEvent->kick();
|
auto kick = plEvent->kick();
|
||||||
if (plEvent->powerLevelForUser(room->localMember().id()) < kick) {
|
if (plEvent->powerLevelForUser(room->localMember().id()) < kick) {
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18n("You are not allowed to kick users from this room."));
|
Q_EMIT Controller::instance().showMessage(Controller::Error, i18n("You are not allowed to kick users from this room."));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) {
|
if (plEvent->powerLevelForUser(room->localMember().id()) <= plEvent->powerLevelForUser(parts[0])) {
|
||||||
Q_EMIT room->showMessage(
|
Q_EMIT Controller::instance().showMessage(
|
||||||
NeoChatRoom::Error,
|
Controller::Error,
|
||||||
i18nc("You are not allowed to kick <username> from this room", "You are not allowed to kick %1 from this room.", parts[0]));
|
i18nc("You are not allowed to kick <username> from this room", "You are not allowed to kick %1 from this room.", parts[0]));
|
||||||
return QString();
|
return QString();
|
||||||
}
|
}
|
||||||
room->kickMember(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
|
room->kickMember(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
|
||||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was kicked from this room.", "%1 was kicked from this room.", parts[0]));
|
Q_EMIT Controller::instance().showMessage(Controller::Positive,
|
||||||
|
i18nc("<username> was kicked from this room.", "%1 was kicked from this room.", parts[0]));
|
||||||
return QString();
|
return QString();
|
||||||
},
|
},
|
||||||
false,
|
false,
|
||||||
|
|||||||
@@ -9,18 +9,16 @@
|
|||||||
#include "customemojimodel.h"
|
#include "customemojimodel.h"
|
||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
#include "roommanager.h"
|
||||||
#include "userlistmodel.h"
|
#include "userlistmodel.h"
|
||||||
|
|
||||||
CompletionModel::CompletionModel(QObject *parent)
|
CompletionModel::CompletionModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
, m_filterModel(new CompletionProxyModel())
|
, m_filterModel(new CompletionProxyModel())
|
||||||
, m_userListModel(new UserListModel(this))
|
, m_userListModel(RoomManager::instance().userListModel())
|
||||||
, m_emojiModel(new QConcatenateTablesProxyModel(this))
|
, m_emojiModel(new QConcatenateTablesProxyModel(this))
|
||||||
{
|
{
|
||||||
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
|
connect(this, &CompletionModel::textChanged, this, &CompletionModel::updateCompletion);
|
||||||
connect(this, &CompletionModel::roomChanged, this, [this]() {
|
|
||||||
m_userListModel->setRoom(m_room);
|
|
||||||
});
|
|
||||||
m_emojiModel->addSourceModel(&CustomEmojiModel::instance());
|
m_emojiModel->addSourceModel(&CustomEmojiModel::instance());
|
||||||
m_emojiModel->addSourceModel(&EmojiModel::instance());
|
m_emojiModel->addSourceModel(&EmojiModel::instance());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include "customemojimodel.h"
|
|
||||||
#include <QRegularExpression>
|
|
||||||
|
|
||||||
class NeoChatConnection;
|
|
||||||
|
|
||||||
struct CustomEmoji {
|
|
||||||
QString name; // with :semicolons:
|
|
||||||
QString url; // mxc://
|
|
||||||
QRegularExpression regexp;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct CustomEmojiModel::Private {
|
|
||||||
QPointer<NeoChatConnection> connection;
|
|
||||||
QList<CustomEmoji> emojies;
|
|
||||||
};
|
|
||||||
@@ -26,7 +26,7 @@ int EmojiModel::rowCount(const QModelIndex &parent) const
|
|||||||
{
|
{
|
||||||
Q_UNUSED(parent);
|
Q_UNUSED(parent);
|
||||||
int total = 0;
|
int total = 0;
|
||||||
for (const auto &category : _emojis) {
|
for (const auto &category : std::as_const(_emojis)) {
|
||||||
total += category.count();
|
total += category.count();
|
||||||
}
|
}
|
||||||
return total;
|
return total;
|
||||||
@@ -35,7 +35,7 @@ int EmojiModel::rowCount(const QModelIndex &parent) const
|
|||||||
QVariant EmojiModel::data(const QModelIndex &index, int role) const
|
QVariant EmojiModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
auto row = index.row();
|
auto row = index.row();
|
||||||
for (const auto &category : _emojis) {
|
for (const auto &category : std::as_const(_emojis)) {
|
||||||
if (row >= category.count()) {
|
if (row >= category.count()) {
|
||||||
row -= category.count();
|
row -= category.count();
|
||||||
continue;
|
continue;
|
||||||
@@ -79,7 +79,8 @@ QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
|
|||||||
{
|
{
|
||||||
QVariantList result;
|
QVariantList result;
|
||||||
|
|
||||||
for (const auto &e : _emojis.values()) {
|
const auto &values = _emojis.values();
|
||||||
|
for (const auto &e : values) {
|
||||||
for (const auto &variant : e) {
|
for (const auto &variant : e) {
|
||||||
const auto &emoji = qvariant_cast<Emoji>(variant);
|
const auto &emoji = qvariant_cast<Emoji>(variant);
|
||||||
if (emoji.shortName.contains(filter, Qt::CaseInsensitive)) {
|
if (emoji.shortName.contains(filter, Qt::CaseInsensitive)) {
|
||||||
@@ -121,7 +122,8 @@ QVariantList EmojiModel::emojis(Category category) const
|
|||||||
}
|
}
|
||||||
if (category == HistoryNoCustom) {
|
if (category == HistoryNoCustom) {
|
||||||
QVariantList list;
|
QVariantList list;
|
||||||
for (const auto &e : emojiHistory()) {
|
const auto &history = emojiHistory();
|
||||||
|
for (const auto &e : history) {
|
||||||
auto emoji = qvariant_cast<Emoji>(e);
|
auto emoji = qvariant_cast<Emoji>(e);
|
||||||
if (!emoji.isCustom) {
|
if (!emoji.isCustom) {
|
||||||
list.append(e);
|
list.append(e);
|
||||||
@@ -224,8 +226,9 @@ QVariantList EmojiModel::categoriesWithCustom() const
|
|||||||
QVariantList EmojiModel::emojiHistory() const
|
QVariantList EmojiModel::emojiHistory() const
|
||||||
{
|
{
|
||||||
QVariantList list;
|
QVariantList list;
|
||||||
for (const auto &historicEmoji : lastUsedEmojis()) {
|
const auto &lastUsed = lastUsedEmojis();
|
||||||
for (const auto &emojiCategory : _emojis) {
|
for (const auto &historicEmoji : lastUsed) {
|
||||||
|
for (const auto &emojiCategory : std::as_const(_emojis)) {
|
||||||
for (const auto &emoji : emojiCategory) {
|
for (const auto &emoji : emojiCategory) {
|
||||||
if (qvariant_cast<Emoji>(emoji).shortName == historicEmoji) {
|
if (qvariant_cast<Emoji>(emoji).shortName == historicEmoji) {
|
||||||
list.append(emoji);
|
list.append(emoji);
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ QVariant LiveLocationsModel::data(const QModelIndex &index, int roleName) const
|
|||||||
case AssetRole:
|
case AssetRole:
|
||||||
return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
||||||
case AuthorRole:
|
case AuthorRole:
|
||||||
return m_room->getUser(data.senderId);
|
return QVariant::fromValue(m_room->member(data.senderId));
|
||||||
case IsLiveRole: {
|
case IsLiveRole: {
|
||||||
if (!data.beaconInfo["live"_ls].toBool()) {
|
if (!data.beaconInfo["live"_ls].toBool()) {
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ void LocationsModel::addLocation(const RoomMessageEvent *event)
|
|||||||
.latitude = latitude,
|
.latitude = latitude,
|
||||||
.longitude = longitude,
|
.longitude = longitude,
|
||||||
.content = event->contentJson(),
|
.content = event->contentJson(),
|
||||||
.author = event->senderId(),
|
.member = m_room->member(event->senderId()),
|
||||||
};
|
};
|
||||||
endInsertRows();
|
endInsertRows();
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ QVariant LocationsModel::data(const QModelIndex &index, int roleName) const
|
|||||||
} else if (roleName == AssetRole) {
|
} else if (roleName == AssetRole) {
|
||||||
return m_locations[row].content["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
return m_locations[row].content["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
||||||
} else if (roleName == AuthorRole) {
|
} else if (roleName == AuthorRole) {
|
||||||
return m_room->getUser(m_locations[row].author);
|
return QVariant::fromValue(m_locations[row].member);
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
#include <Quotient/events/roommessageevent.h>
|
#include <Quotient/events/roommessageevent.h>
|
||||||
#include <Quotient/user.h>
|
#include <Quotient/roommember.h>
|
||||||
|
|
||||||
class LocationsModel : public QAbstractListModel
|
class LocationsModel : public QAbstractListModel
|
||||||
{
|
{
|
||||||
@@ -57,7 +57,7 @@ private:
|
|||||||
float latitude;
|
float latitude;
|
||||||
float longitude;
|
float longitude;
|
||||||
QJsonObject content;
|
QJsonObject content;
|
||||||
QString author;
|
Quotient::RoomMember member;
|
||||||
};
|
};
|
||||||
QList<LocationData> m_locations;
|
QList<LocationData> m_locations;
|
||||||
void addLocation(const Quotient::RoomMessageEvent *event);
|
void addLocation(const Quotient::RoomMessageEvent *event);
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <Quotient/events/roommessageevent.h>
|
#include <Quotient/events/roommessageevent.h>
|
||||||
#include <Quotient/room.h>
|
#include <Quotient/room.h>
|
||||||
|
|
||||||
|
#include "messagecontentmodel.h"
|
||||||
#include "messageeventmodel.h"
|
#include "messageeventmodel.h"
|
||||||
#include "messagefiltermodel.h"
|
#include "messagefiltermodel.h"
|
||||||
|
|
||||||
@@ -29,40 +30,6 @@ bool MediaMessageFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex
|
|||||||
|
|
||||||
QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
||||||
{
|
{
|
||||||
if (role == SourceRole) {
|
|
||||||
if (mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("image"))) {
|
|
||||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("source")].toUrl();
|
|
||||||
} else if (mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("video"))) {
|
|
||||||
auto progressInfo = mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>();
|
|
||||||
|
|
||||||
if (progressInfo.completed()) {
|
|
||||||
return mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>().localPath;
|
|
||||||
} else {
|
|
||||||
return QUrl();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return QUrl();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (role == TempSourceRole) {
|
|
||||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("tempInfo")].toMap()[QStringLiteral("source")].toUrl();
|
|
||||||
}
|
|
||||||
if (role == TypeRole) {
|
|
||||||
if (mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QLatin1String("mimeType")].toString().contains(QLatin1String("image"))) {
|
|
||||||
return MediaType::Image;
|
|
||||||
} else {
|
|
||||||
return MediaType::Video;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (role == CaptionRole) {
|
|
||||||
return mapToSource(index).data(Qt::DisplayRole);
|
|
||||||
}
|
|
||||||
if (role == SourceWidthRole) {
|
|
||||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("width")].toFloat();
|
|
||||||
}
|
|
||||||
if (role == SourceHeightRole) {
|
|
||||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("height")].toFloat();
|
|
||||||
}
|
|
||||||
// We need to catch this one and return true if the next media object was
|
// We need to catch this one and return true if the next media object was
|
||||||
// on a different day.
|
// on a different day.
|
||||||
if (role == MessageEventModel::ShowSectionRole) {
|
if (role == MessageEventModel::ShowSectionRole) {
|
||||||
@@ -70,6 +37,45 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
|||||||
const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(MessageEventModel::TimeRole).toDateTime().toLocalTime().date();
|
const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(MessageEventModel::TimeRole).toDateTime().toLocalTime().date();
|
||||||
return day != previousEventDay;
|
return day != previousEventDay;
|
||||||
}
|
}
|
||||||
|
// Catch and force the author to be shown for all rows
|
||||||
|
if (role == MessageEventModel::ContentModelRole) {
|
||||||
|
const auto model = qvariant_cast<MessageContentModel *>(mapToSource(index).data(MessageEventModel::ContentModelRole));
|
||||||
|
if (model != nullptr) {
|
||||||
|
model->setShowAuthor(true);
|
||||||
|
}
|
||||||
|
return QVariant::fromValue<MessageContentModel *>(model);
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantMap mediaInfo = mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap();
|
||||||
|
|
||||||
|
if (role == TempSourceRole) {
|
||||||
|
return mediaInfo[QStringLiteral("tempInfo")].toMap()[QStringLiteral("source")].toUrl();
|
||||||
|
}
|
||||||
|
if (role == CaptionRole) {
|
||||||
|
return mapToSource(index).data(Qt::DisplayRole);
|
||||||
|
}
|
||||||
|
if (role == SourceWidthRole) {
|
||||||
|
return mediaInfo[QStringLiteral("width")].toFloat();
|
||||||
|
}
|
||||||
|
if (role == SourceHeightRole) {
|
||||||
|
return mediaInfo[QStringLiteral("height")].toFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool isVideo = mediaInfo[QStringLiteral("mimeType")].toString().contains(QStringLiteral("video"));
|
||||||
|
|
||||||
|
if (role == TypeRole) {
|
||||||
|
return (isVideo) ? MediaType::Video : MediaType::Image;
|
||||||
|
}
|
||||||
|
if (role == SourceRole) {
|
||||||
|
if (isVideo) {
|
||||||
|
auto progressInfo = mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>();
|
||||||
|
if (progressInfo.completed()) {
|
||||||
|
return mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>().localPath;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return mediaInfo[QStringLiteral("source")].toUrl();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return sourceModel()->data(mapToSource(index), role);
|
return sourceModel()->data(mapToSource(index), role);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#include "messagecontentmodel.h"
|
#include "messagecontentmodel.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
|
#include "neochatroommember.h"
|
||||||
|
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
|
|
||||||
@@ -30,20 +31,23 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply)
|
MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply, bool isPending)
|
||||||
: QAbstractListModel(nullptr)
|
: QAbstractListModel(nullptr)
|
||||||
, m_room(room)
|
, m_room(room)
|
||||||
, m_eventId(event != nullptr ? event->id() : QString())
|
, m_eventId(event != nullptr ? event->id() : QString())
|
||||||
, m_event(event)
|
, m_eventSenderId(event != nullptr ? event->senderId() : QString())
|
||||||
|
, m_isPending(isPending)
|
||||||
, m_isReply(isReply)
|
, m_isReply(isReply)
|
||||||
{
|
{
|
||||||
|
intiializeEvent(event);
|
||||||
initializeModel();
|
initializeModel();
|
||||||
}
|
}
|
||||||
|
|
||||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply)
|
MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply, bool isPending)
|
||||||
: QAbstractListModel(nullptr)
|
: QAbstractListModel(nullptr)
|
||||||
, m_room(room)
|
, m_room(room)
|
||||||
, m_eventId(eventId)
|
, m_eventId(eventId)
|
||||||
|
, m_isPending(isPending)
|
||||||
, m_isReply(isReply)
|
, m_isReply(isReply)
|
||||||
{
|
{
|
||||||
initializeModel();
|
initializeModel();
|
||||||
@@ -59,10 +63,10 @@ void MessageContentModel::initializeModel()
|
|||||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
|
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
|
||||||
if (m_room != nullptr) {
|
if (m_room != nullptr) {
|
||||||
if (eventId == m_eventId) {
|
if (eventId == m_eventId) {
|
||||||
m_event = m_room->getEvent(eventId);
|
m_event = loadEvent<RoomEvent>(m_room->getEvent(eventId)->fullJson());
|
||||||
Q_EMIT eventUpdated();
|
Q_EMIT eventUpdated();
|
||||||
updateReplyModel();
|
updateReplyModel();
|
||||||
updateComponents();
|
resetContent();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,80 +79,127 @@ void MessageContentModel::initializeModel()
|
|||||||
|
|
||||||
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
||||||
if (m_room != nullptr && m_event != nullptr) {
|
if (m_room != nullptr && m_event != nullptr) {
|
||||||
if (m_event->id() == serverEvent->id()) {
|
if (m_eventId == serverEvent->id()) {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_event = serverEvent;
|
m_isPending = false;
|
||||||
Q_EMIT eventUpdated();
|
intiializeEvent(serverEvent);
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
|
connect(m_room, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
|
||||||
if (m_room != nullptr && m_event != nullptr) {
|
if (m_room != nullptr && m_event != nullptr) {
|
||||||
if (m_event->id() == newEvent->id()) {
|
if (m_eventId == newEvent->id()) {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_event = newEvent;
|
intiializeEvent(newEvent);
|
||||||
Q_EMIT eventUpdated();
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) {
|
connect(m_room, &NeoChatRoom::newFileTransfer, this, [this](const QString &eventId) {
|
||||||
if (m_event != nullptr && eventId == m_event->id()) {
|
if (m_event != nullptr && eventId == m_eventId) {
|
||||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) {
|
connect(m_room, &NeoChatRoom::fileTransferProgress, this, [this](const QString &eventId) {
|
||||||
if (m_event != nullptr && eventId == m_event->id()) {
|
if (m_event != nullptr && eventId == m_eventId) {
|
||||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
|
connect(m_room, &NeoChatRoom::fileTransferCompleted, this, [this](const QString &eventId) {
|
||||||
if (m_event != nullptr && eventId == m_event->id()) {
|
if (m_room != nullptr && m_event != nullptr && eventId == m_eventId) {
|
||||||
updateComponents();
|
resetContent();
|
||||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||||
|
|
||||||
QString mxcUrl;
|
|
||||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
|
||||||
if (event->hasFileContent()) {
|
|
||||||
mxcUrl = event->content()->fileInfo()->url().toString();
|
|
||||||
}
|
|
||||||
} else if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
|
|
||||||
mxcUrl = event->image().fileInfo()->url().toString();
|
|
||||||
}
|
|
||||||
if (mxcUrl.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto localPath = m_room->fileTransferInfo(m_event->id()).localPath.toLocalFile();
|
|
||||||
auto config = KSharedConfig::openStateConfig(QStringLiteral("neochatdownloads"))->group(QStringLiteral("downloads"));
|
|
||||||
config.writePathEntry(mxcUrl.mid(6), localPath);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) {
|
connect(m_room, &NeoChatRoom::fileTransferFailed, this, [this](const QString &eventId) {
|
||||||
if (m_event != nullptr && eventId == m_event->id()) {
|
if (m_event != nullptr && eventId == m_eventId) {
|
||||||
updateComponents();
|
resetContent();
|
||||||
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
Q_EMIT dataChanged(index(0), index(rowCount() - 1), {FileTransferInfoRole});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room->editCache(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) {
|
connect(m_room->editCache(), &ChatBarCache::relationIdChanged, this, [this](const QString &oldEventId, const QString &newEventId) {
|
||||||
if (m_event != nullptr && (oldEventId == m_event->id() || newEventId == m_event->id())) {
|
if (m_event != nullptr && (oldEventId == m_eventId || newEventId == m_eventId)) {
|
||||||
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
// HACK: Because DelegateChooser can't switch the delegate on dataChanged it has to think there is a new delegate.
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
updateComponents(newEventId == m_event->id());
|
resetContent(newEventId == m_eventId);
|
||||||
endResetModel();
|
endResetModel();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
||||||
updateComponents();
|
resetContent();
|
||||||
});
|
});
|
||||||
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
|
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
|
||||||
updateComponents();
|
resetContent();
|
||||||
|
});
|
||||||
|
connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) {
|
||||||
|
if (m_room != nullptr && m_event != nullptr) {
|
||||||
|
if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) {
|
||||||
|
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_room, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
|
||||||
|
if (m_room != nullptr && m_event != nullptr) {
|
||||||
|
if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) {
|
||||||
|
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (m_event != nullptr) {
|
if (m_event != nullptr) {
|
||||||
updateReplyModel();
|
updateReplyModel();
|
||||||
}
|
}
|
||||||
updateComponents();
|
resetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageContentModel::intiializeEvent(const QString &eventId)
|
||||||
|
{
|
||||||
|
const auto newEvent = m_room->getEvent(eventId);
|
||||||
|
if (newEvent != nullptr) {
|
||||||
|
intiializeEvent(newEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageContentModel::intiializeEvent(const Quotient::RoomEvent *event)
|
||||||
|
{
|
||||||
|
m_event = loadEvent<RoomEvent>(event->fullJson());
|
||||||
|
auto senderId = event->senderId();
|
||||||
|
// A pending event might not have a sender ID set yet but in that case it must
|
||||||
|
// be the local member.
|
||||||
|
if (senderId.isEmpty()) {
|
||||||
|
senderId = m_room->localMember().id();
|
||||||
|
}
|
||||||
|
if (m_eventSenderObject == nullptr) {
|
||||||
|
m_eventSenderObject = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_room, senderId));
|
||||||
|
}
|
||||||
|
Q_EMIT eventUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool MessageContentModel::showAuthor() const
|
||||||
|
{
|
||||||
|
return m_showAuthor;
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageContentModel::setShowAuthor(bool showAuthor)
|
||||||
|
{
|
||||||
|
if (showAuthor == m_showAuthor) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
m_showAuthor = showAuthor;
|
||||||
|
|
||||||
|
if (m_event != nullptr) {
|
||||||
|
if (showAuthor) {
|
||||||
|
beginInsertRows({}, 0, 0);
|
||||||
|
m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}});
|
||||||
|
endInsertRows();
|
||||||
|
} else {
|
||||||
|
beginRemoveRows({}, 0, 0);
|
||||||
|
m_components.remove(0, 1);
|
||||||
|
endRemoveRows();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Q_EMIT showAuthorChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
|
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
|
||||||
@@ -164,7 +215,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
EventHandler eventHandler(m_room, m_event);
|
EventHandler eventHandler(m_room, m_event.get());
|
||||||
const auto component = m_components[index.row()];
|
const auto component = m_components[index.row()];
|
||||||
|
|
||||||
if (role == DisplayRole) {
|
if (role == DisplayRole) {
|
||||||
@@ -193,14 +244,30 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
if (role == EventIdRole) {
|
if (role == EventIdRole) {
|
||||||
return eventHandler.getId();
|
return eventHandler.getId();
|
||||||
}
|
}
|
||||||
|
if (role == TimeRole) {
|
||||||
|
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||||
|
return m_event->transactionId() == pendingEvent->transactionId();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||||
|
return eventHandler.getTime(m_isPending, lastUpdated);
|
||||||
|
}
|
||||||
|
if (role == TimeStringRole) {
|
||||||
|
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||||
|
return m_event->transactionId() == pendingEvent->transactionId();
|
||||||
|
});
|
||||||
|
|
||||||
|
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||||
|
return eventHandler.getTimeString(QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||||
|
}
|
||||||
if (role == AuthorRole) {
|
if (role == AuthorRole) {
|
||||||
return eventHandler.getAuthor(false);
|
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
|
||||||
}
|
}
|
||||||
if (role == MediaInfoRole) {
|
if (role == MediaInfoRole) {
|
||||||
return eventHandler.getMediaInfo();
|
return eventHandler.getMediaInfo();
|
||||||
}
|
}
|
||||||
if (role == FileTransferInfoRole) {
|
if (role == FileTransferInfoRole) {
|
||||||
return QVariant::fromValue(fileInfo());
|
return QVariant::fromValue(m_room->cachedFileTransferInfo(m_event.get()));
|
||||||
}
|
}
|
||||||
if (role == ItineraryModelRole) {
|
if (role == ItineraryModelRole) {
|
||||||
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
||||||
@@ -215,7 +282,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
return eventHandler.getLocationAssetType();
|
return eventHandler.getLocationAssetType();
|
||||||
}
|
}
|
||||||
if (role == PollHandlerRole) {
|
if (role == PollHandlerRole) {
|
||||||
return QVariant::fromValue<PollHandler *>(m_room->poll(m_event->id()));
|
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
|
||||||
}
|
}
|
||||||
if (role == IsReplyRole) {
|
if (role == IsReplyRole) {
|
||||||
return eventHandler.hasReply();
|
return eventHandler.hasReply();
|
||||||
@@ -224,7 +291,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
|||||||
return eventHandler.getReplyId();
|
return eventHandler.getReplyId();
|
||||||
}
|
}
|
||||||
if (role == ReplyAuthorRole) {
|
if (role == ReplyAuthorRole) {
|
||||||
return eventHandler.getReplyAuthor();
|
return QVariant::fromValue(eventHandler.getReplyAuthor());
|
||||||
}
|
}
|
||||||
if (role == ReplyContentModelRole) {
|
if (role == ReplyContentModelRole) {
|
||||||
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
||||||
@@ -254,6 +321,8 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
|||||||
roles[ComponentTypeRole] = "componentType";
|
roles[ComponentTypeRole] = "componentType";
|
||||||
roles[ComponentAttributesRole] = "componentAttributes";
|
roles[ComponentAttributesRole] = "componentAttributes";
|
||||||
roles[EventIdRole] = "eventId";
|
roles[EventIdRole] = "eventId";
|
||||||
|
roles[TimeRole] = "time";
|
||||||
|
roles[TimeStringRole] = "timeString";
|
||||||
roles[AuthorRole] = "author";
|
roles[AuthorRole] = "author";
|
||||||
roles[MediaInfoRole] = "mediaInfo";
|
roles[MediaInfoRole] = "mediaInfo";
|
||||||
roles[FileTransferInfoRole] = "fileTransferInfo";
|
roles[FileTransferInfoRole] = "fileTransferInfo";
|
||||||
@@ -270,7 +339,7 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
|||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageContentModel::updateComponents(bool isEditing)
|
void MessageContentModel::resetModel()
|
||||||
{
|
{
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
m_components.clear();
|
m_components.clear();
|
||||||
@@ -281,35 +350,63 @@ void MessageContentModel::updateComponents(bool isEditing)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (m_showAuthor) {
|
||||||
|
m_components += MessageComponent{MessageComponentType::Author, QString(), {}};
|
||||||
|
}
|
||||||
|
|
||||||
|
m_components += messageContentComponents();
|
||||||
|
endResetModel();
|
||||||
|
}
|
||||||
|
|
||||||
|
void MessageContentModel::resetContent(bool isEditing)
|
||||||
|
{
|
||||||
|
Q_ASSERT(m_event != nullptr);
|
||||||
|
|
||||||
|
const auto startRow = m_components[0].type == MessageComponentType::Author ? 1 : 0;
|
||||||
|
beginRemoveRows({}, startRow, rowCount() - 1);
|
||||||
|
m_components.remove(startRow, rowCount() - startRow);
|
||||||
|
endRemoveRows();
|
||||||
|
|
||||||
|
const auto newComponents = messageContentComponents(isEditing);
|
||||||
|
if (newComponents.size() == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
beginInsertRows({}, startRow, startRow + newComponents.size() - 1);
|
||||||
|
m_components += newComponents;
|
||||||
|
endInsertRows();
|
||||||
|
}
|
||||||
|
|
||||||
|
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing)
|
||||||
|
{
|
||||||
|
QList<MessageComponent> newComponents;
|
||||||
|
|
||||||
if (eventCast<const Quotient::RoomMessageEvent>(m_event)
|
if (eventCast<const Quotient::RoomMessageEvent>(m_event)
|
||||||
&& eventCast<const Quotient::RoomMessageEvent>(m_event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
&& eventCast<const Quotient::RoomMessageEvent>(m_event)->rawMsgtype() == QStringLiteral("m.key.verification.request")) {
|
||||||
m_components += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
newComponents += MessageComponent{MessageComponentType::Verification, QString(), {}};
|
||||||
endResetModel();
|
return newComponents;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_event->isRedacted()) {
|
if (m_event->isRedacted()) {
|
||||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
newComponents += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||||
endResetModel();
|
return newComponents;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_replyModel != nullptr) {
|
if (m_replyModel != nullptr) {
|
||||||
m_components += MessageComponent{MessageComponentType::Reply, QString(), {}};
|
newComponents += MessageComponent{MessageComponentType::Reply, QString(), {}};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isEditing) {
|
if (isEditing) {
|
||||||
m_components += MessageComponent{MessageComponentType::Edit, QString(), {}};
|
newComponents += MessageComponent{MessageComponentType::Edit, QString(), {}};
|
||||||
} else {
|
} else {
|
||||||
EventHandler eventHandler(m_room, m_event);
|
EventHandler eventHandler(m_room, m_event.get());
|
||||||
m_components.append(componentsForType(eventHandler.messageComponentType()));
|
newComponents.append(componentsForType(eventHandler.messageComponentType()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_room->urlPreviewEnabled()) {
|
if (m_room->urlPreviewEnabled()) {
|
||||||
addLinkPreviews();
|
newComponents = addLinkPreviews(newComponents);
|
||||||
}
|
}
|
||||||
|
|
||||||
endResetModel();
|
return newComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageContentModel::updateReplyModel()
|
void MessageContentModel::updateReplyModel()
|
||||||
@@ -318,7 +415,7 @@ void MessageContentModel::updateReplyModel()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
EventHandler eventHandler(m_room, m_event);
|
EventHandler eventHandler(m_room, m_event.get());
|
||||||
if (!eventHandler.hasReply()) {
|
if (!eventHandler.hasReply()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -347,36 +444,52 @@ QList<MessageComponent> MessageContentModel::componentsForType(MessageComponentT
|
|||||||
QList<MessageComponent> components;
|
QList<MessageComponent> components;
|
||||||
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
components += MessageComponent{MessageComponentType::File, QString(), {}};
|
||||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||||
auto body = EventHandler::rawMessageBody(*event);
|
|
||||||
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
|
||||||
if (m_emptyItinerary) {
|
if (m_emptyItinerary) {
|
||||||
auto fileTransferInfo = fileInfo();
|
if (!m_isReply) {
|
||||||
|
auto fileTransferInfo = m_room->cachedFileTransferInfo(m_event.get());
|
||||||
|
|
||||||
#ifndef Q_OS_ANDROID
|
#ifndef Q_OS_ANDROID
|
||||||
KSyntaxHighlighting::Repository repository;
|
Q_ASSERT(event->content() != nullptr && event->content()->fileInfo() != nullptr);
|
||||||
const auto definitionForFile = repository.definitionForFileName(fileTransferInfo.localPath.toString());
|
const QMimeType mimeType = event->content()->fileInfo()->mimeType;
|
||||||
if (definitionForFile.isValid() || QFileInfo(fileTransferInfo.localPath.path()).suffix() == QStringLiteral("txt")) {
|
if (mimeType.name() == QStringLiteral("text/plain") || mimeType.parentMimeTypes().contains(QStringLiteral("text/plain"))) {
|
||||||
QFile file(fileTransferInfo.localPath.path());
|
QString originalName = event->content()->fileInfo()->originalName;
|
||||||
file.open(QIODevice::ReadOnly);
|
if (originalName.isEmpty()) {
|
||||||
components += MessageComponent{MessageComponentType::Code,
|
originalName = event->plainBody();
|
||||||
QString::fromStdString(file.readAll().toStdString()),
|
}
|
||||||
{{QStringLiteral("class"), definitionForFile.name()}}};
|
KSyntaxHighlighting::Repository repository;
|
||||||
}
|
KSyntaxHighlighting::Definition definitionForFile = repository.definitionForFileName(originalName);
|
||||||
|
if (!definitionForFile.isValid()) {
|
||||||
|
definitionForFile = repository.definitionForMimeType(mimeType.name());
|
||||||
|
}
|
||||||
|
|
||||||
|
QFile file(fileTransferInfo.localPath.path());
|
||||||
|
file.open(QIODevice::ReadOnly);
|
||||||
|
components += MessageComponent{MessageComponentType::Code,
|
||||||
|
QString::fromStdString(file.readAll().toStdString()),
|
||||||
|
{{QStringLiteral("class"), definitionForFile.name()}}};
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (FileType::instance().fileHasImage(fileTransferInfo.localPath)) {
|
if (FileType::instance().fileHasImage(fileTransferInfo.localPath)) {
|
||||||
QImageReader reader(fileTransferInfo.localPath.path());
|
QImageReader reader(fileTransferInfo.localPath.path());
|
||||||
components += MessageComponent{MessageComponentType::Pdf, QString(), {{QStringLiteral("size"), reader.size()}}};
|
components += MessageComponent{MessageComponentType::Pdf, QString(), {{QStringLiteral("size"), reader.size()}}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (m_itineraryModel != nullptr) {
|
||||||
|
components += MessageComponent{MessageComponentType::Itinerary, QString(), {}};
|
||||||
|
if (m_itineraryModel->rowCount() > 0) {
|
||||||
|
updateItineraryModel();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
updateItineraryModel();
|
updateItineraryModel();
|
||||||
if (m_itineraryModel != nullptr) {
|
|
||||||
components += MessageComponent{MessageComponentType::Itinerary, QString(), {}};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
auto body = EventHandler::rawMessageBody(*event);
|
||||||
|
components += TextHandler().textComponents(body, EventHandler::messageBodyInputFormat(*event), m_room, event, event->isReplaced());
|
||||||
return components;
|
return components;
|
||||||
}
|
}
|
||||||
case MessageComponentType::Image:
|
case MessageComponentType::Image:
|
||||||
|
case MessageComponentType::Audio:
|
||||||
case MessageComponentType::Video: {
|
case MessageComponentType::Video: {
|
||||||
if (!m_event->is<StickerEvent>()) {
|
if (!m_event->is<StickerEvent>()) {
|
||||||
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
const auto event = eventCast<const Quotient::RoomMessageEvent>(m_event);
|
||||||
@@ -418,24 +531,26 @@ MessageComponent MessageContentModel::linkPreviewComponent(const QUrl &link)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageContentModel::addLinkPreviews()
|
QList<MessageComponent> MessageContentModel::addLinkPreviews(QList<MessageComponent> inputComponents)
|
||||||
{
|
{
|
||||||
int i = 0;
|
int i = 0;
|
||||||
while (i < m_components.size()) {
|
while (i < inputComponents.size()) {
|
||||||
const auto component = m_components.at(i);
|
const auto component = inputComponents.at(i);
|
||||||
if (component.type == MessageComponentType::Text || component.type == MessageComponentType::Quote) {
|
if (component.type == MessageComponentType::Text || component.type == MessageComponentType::Quote) {
|
||||||
if (LinkPreviewer::hasPreviewableLinks(component.content)) {
|
if (LinkPreviewer::hasPreviewableLinks(component.content)) {
|
||||||
const auto links = LinkPreviewer::linkPreviews(component.content);
|
const auto links = LinkPreviewer::linkPreviews(component.content);
|
||||||
for (qsizetype j = 0; j < links.size(); ++j) {
|
for (qsizetype j = 0; j < links.size(); ++j) {
|
||||||
const auto linkPreview = linkPreviewComponent(links[j]);
|
const auto linkPreview = linkPreviewComponent(links[j]);
|
||||||
if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) {
|
if (!m_removedLinkPreviews.contains(links[j]) && !linkPreview.isEmpty()) {
|
||||||
m_components.insert(i + j + 1, linkPreview);
|
inputComponents.insert(i + j + 1, linkPreview);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
i++;
|
i++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return inputComponents;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageContentModel::closeLinkPreview(int row)
|
void MessageContentModel::closeLinkPreview(int row)
|
||||||
@@ -445,8 +560,7 @@ void MessageContentModel::closeLinkPreview(int row)
|
|||||||
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
|
m_removedLinkPreviews += m_components[row].attributes["link"_ls].toUrl();
|
||||||
m_components.remove(row);
|
m_components.remove(row);
|
||||||
m_components.squeeze();
|
m_components.squeeze();
|
||||||
updateComponents();
|
resetContent();
|
||||||
endResetModel();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -458,7 +572,7 @@ void MessageContentModel::updateItineraryModel()
|
|||||||
|
|
||||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
||||||
if (event->hasFileContent()) {
|
if (event->hasFileContent()) {
|
||||||
auto filePath = fileInfo().localPath;
|
auto filePath = m_room->cachedFileTransferInfo(m_event.get()).localPath;
|
||||||
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
if (filePath.isEmpty() && m_itineraryModel != nullptr) {
|
||||||
delete m_itineraryModel;
|
delete m_itineraryModel;
|
||||||
m_itineraryModel = nullptr;
|
m_itineraryModel = nullptr;
|
||||||
@@ -467,17 +581,17 @@ void MessageContentModel::updateItineraryModel()
|
|||||||
m_itineraryModel = new ItineraryModel(this);
|
m_itineraryModel = new ItineraryModel(this);
|
||||||
connect(m_itineraryModel, &ItineraryModel::loaded, this, [this]() {
|
connect(m_itineraryModel, &ItineraryModel::loaded, this, [this]() {
|
||||||
if (m_itineraryModel->rowCount() == 0) {
|
if (m_itineraryModel->rowCount() == 0) {
|
||||||
|
m_emptyItinerary = true;
|
||||||
m_itineraryModel->deleteLater();
|
m_itineraryModel->deleteLater();
|
||||||
m_itineraryModel = nullptr;
|
m_itineraryModel = nullptr;
|
||||||
m_emptyItinerary = true;
|
resetContent();
|
||||||
updateComponents();
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_itineraryModel, &ItineraryModel::loadErrorOccurred, this, [this]() {
|
connect(m_itineraryModel, &ItineraryModel::loadErrorOccurred, this, [this]() {
|
||||||
|
m_emptyItinerary = true;
|
||||||
m_itineraryModel->deleteLater();
|
m_itineraryModel->deleteLater();
|
||||||
m_itineraryModel = nullptr;
|
m_itineraryModel = nullptr;
|
||||||
m_emptyItinerary = true;
|
resetContent();
|
||||||
updateComponents();
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
m_itineraryModel->setPath(filePath.toString());
|
m_itineraryModel->setPath(filePath.toString());
|
||||||
@@ -486,42 +600,4 @@ void MessageContentModel::updateItineraryModel()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
FileTransferInfo MessageContentModel::fileInfo() const
|
|
||||||
{
|
|
||||||
if (m_room == nullptr || m_event == nullptr) {
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
QString mxcUrl;
|
|
||||||
int total;
|
|
||||||
if (auto event = eventCast<const Quotient::RoomMessageEvent>(m_event)) {
|
|
||||||
if (event->hasFileContent()) {
|
|
||||||
mxcUrl = event->content()->fileInfo()->url().toString();
|
|
||||||
total = event->content()->fileInfo()->payloadSize;
|
|
||||||
}
|
|
||||||
} else if (auto event = eventCast<const Quotient::StickerEvent>(m_event)) {
|
|
||||||
mxcUrl = event->image().fileInfo()->url().toString();
|
|
||||||
total = event->image().fileInfo()->payloadSize;
|
|
||||||
}
|
|
||||||
auto config = KSharedConfig::openStateConfig(QStringLiteral("neochatdownloads"))->group(QStringLiteral("downloads"));
|
|
||||||
if (!config.hasKey(mxcUrl.mid(6))) {
|
|
||||||
return m_room->fileTransferInfo(m_event->id());
|
|
||||||
}
|
|
||||||
const auto path = config.readPathEntry(mxcUrl.mid(6), QString());
|
|
||||||
QFileInfo info(path);
|
|
||||||
if (!info.isFile()) {
|
|
||||||
config.deleteEntry(mxcUrl);
|
|
||||||
return m_room->fileTransferInfo(m_event->id());
|
|
||||||
}
|
|
||||||
// TODO: we could check the hash here
|
|
||||||
return FileTransferInfo{
|
|
||||||
.status = FileTransferInfo::Completed,
|
|
||||||
.isUpload = false,
|
|
||||||
.progress = total,
|
|
||||||
.total = total,
|
|
||||||
.localDir = QUrl(info.dir().path()),
|
|
||||||
.localPath = QUrl::fromLocalFile(path),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
#include "moc_messagecontentmodel.cpp"
|
#include "moc_messagecontentmodel.cpp"
|
||||||
|
|||||||
@@ -6,11 +6,13 @@
|
|||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QQmlEngine>
|
#include <QQmlEngine>
|
||||||
|
|
||||||
|
#include <Quotient/events/roomevent.h>
|
||||||
#include <Quotient/room.h>
|
#include <Quotient/room.h>
|
||||||
|
|
||||||
#include "enums/messagecomponenttype.h"
|
#include "enums/messagecomponenttype.h"
|
||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
#include "itinerarymodel.h"
|
#include "itinerarymodel.h"
|
||||||
|
#include "neochatroommember.h"
|
||||||
|
|
||||||
struct MessageComponent {
|
struct MessageComponent {
|
||||||
MessageComponentType::Type type = MessageComponentType::Other;
|
MessageComponentType::Type type = MessageComponentType::Other;
|
||||||
@@ -39,6 +41,11 @@ class MessageContentModel : public QAbstractListModel
|
|||||||
QML_ELEMENT
|
QML_ELEMENT
|
||||||
QML_UNCREATABLE("")
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Whether the author component is being shown.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
* @brief Defines the model roles.
|
* @brief Defines the model roles.
|
||||||
@@ -48,6 +55,8 @@ public:
|
|||||||
ComponentTypeRole, /**< The type of component to visualise the message. */
|
ComponentTypeRole, /**< The type of component to visualise the message. */
|
||||||
ComponentAttributesRole, /**< The attributes of the component. */
|
ComponentAttributesRole, /**< The attributes of the component. */
|
||||||
EventIdRole, /**< The matrix event ID of the event. */
|
EventIdRole, /**< The matrix event ID of the event. */
|
||||||
|
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
|
||||||
|
TimeStringRole, /**< The timestamp for when the event was sent as a string (in QLocale::ShortFormat). */
|
||||||
AuthorRole, /**< The author of the event. */
|
AuthorRole, /**< The author of the event. */
|
||||||
MediaInfoRole, /**< The media info for the event. */
|
MediaInfoRole, /**< The media info for the event. */
|
||||||
FileTransferInfoRole, /**< FileTransferInfo for any downloading files. */
|
FileTransferInfoRole, /**< FileTransferInfo for any downloading files. */
|
||||||
@@ -66,8 +75,11 @@ public:
|
|||||||
};
|
};
|
||||||
Q_ENUM(Roles)
|
Q_ENUM(Roles)
|
||||||
|
|
||||||
explicit MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply = false);
|
explicit MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply = false, bool isPending = false);
|
||||||
MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false);
|
MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false, bool isPending = false);
|
||||||
|
|
||||||
|
bool showAuthor() const;
|
||||||
|
void setShowAuthor(bool showAuthor);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Get the given role value at the given index.
|
* @brief Get the given role value at the given index.
|
||||||
@@ -98,19 +110,28 @@ public:
|
|||||||
Q_INVOKABLE void closeLinkPreview(int row);
|
Q_INVOKABLE void closeLinkPreview(int row);
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
|
void showAuthorChanged();
|
||||||
void eventUpdated();
|
void eventUpdated();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<NeoChatRoom> m_room;
|
QPointer<NeoChatRoom> m_room;
|
||||||
QString m_eventId;
|
QString m_eventId;
|
||||||
const Quotient::RoomEvent *m_event = nullptr;
|
QString m_eventSenderId;
|
||||||
|
std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
|
||||||
|
Quotient::RoomEventPtr m_event;
|
||||||
|
|
||||||
|
bool m_isPending;
|
||||||
|
bool m_showAuthor = true;
|
||||||
bool m_isReply;
|
bool m_isReply;
|
||||||
|
|
||||||
void initializeModel();
|
void initializeModel();
|
||||||
|
void intiializeEvent(const QString &eventId);
|
||||||
|
void intiializeEvent(const Quotient::RoomEvent *event);
|
||||||
|
|
||||||
QList<MessageComponent> m_components;
|
QList<MessageComponent> m_components;
|
||||||
void updateComponents(bool isEditing = false);
|
void resetModel();
|
||||||
|
void resetContent(bool isEditing = false);
|
||||||
|
QList<MessageComponent> messageContentComponents(bool isEditing = false);
|
||||||
|
|
||||||
QPointer<MessageContentModel> m_replyModel;
|
QPointer<MessageContentModel> m_replyModel;
|
||||||
void updateReplyModel();
|
void updateReplyModel();
|
||||||
@@ -119,12 +140,10 @@ private:
|
|||||||
|
|
||||||
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
|
QList<MessageComponent> componentsForType(MessageComponentType::Type type);
|
||||||
MessageComponent linkPreviewComponent(const QUrl &link);
|
MessageComponent linkPreviewComponent(const QUrl &link);
|
||||||
void addLinkPreviews();
|
QList<MessageComponent> addLinkPreviews(QList<MessageComponent> inputComponents);
|
||||||
|
|
||||||
QList<QUrl> m_removedLinkPreviews;
|
QList<QUrl> m_removedLinkPreviews;
|
||||||
|
|
||||||
void updateItineraryModel();
|
void updateItineraryModel();
|
||||||
bool m_emptyItinerary = false;
|
bool m_emptyItinerary = false;
|
||||||
|
|
||||||
Quotient::FileTransferInfo fileInfo() const;
|
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
#include <Quotient/events/redactionevent.h>
|
#include <Quotient/events/redactionevent.h>
|
||||||
#include <Quotient/events/roommessageevent.h>
|
#include <Quotient/events/roommessageevent.h>
|
||||||
#include <Quotient/events/stickerevent.h>
|
#include <Quotient/events/stickerevent.h>
|
||||||
#include <Quotient/user.h>
|
#include <Quotient/roommember.h>
|
||||||
|
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
@@ -26,6 +26,9 @@
|
|||||||
#include "messagecontentmodel.h"
|
#include "messagecontentmodel.h"
|
||||||
#include "models/messagefiltermodel.h"
|
#include "models/messagefiltermodel.h"
|
||||||
#include "models/reactionmodel.h"
|
#include "models/reactionmodel.h"
|
||||||
|
#include "neochatroom.h"
|
||||||
|
#include "neochatroommember.h"
|
||||||
|
#include "readmarkermodel.h"
|
||||||
#include "texthandler.h"
|
#include "texthandler.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -36,7 +39,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[DelegateTypeRole] = "delegateType";
|
roles[DelegateTypeRole] = "delegateType";
|
||||||
roles[EventIdRole] = "eventId";
|
roles[EventIdRole] = "eventId";
|
||||||
roles[TimeRole] = "time";
|
roles[TimeRole] = "time";
|
||||||
roles[TimeStringRole] = "timeString";
|
|
||||||
roles[SectionRole] = "section";
|
roles[SectionRole] = "section";
|
||||||
roles[AuthorRole] = "author";
|
roles[AuthorRole] = "author";
|
||||||
roles[HighlightRole] = "isHighlighted";
|
roles[HighlightRole] = "isHighlighted";
|
||||||
@@ -46,8 +48,6 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
|||||||
roles[ThreadRootRole] = "threadRoot";
|
roles[ThreadRootRole] = "threadRoot";
|
||||||
roles[ShowSectionRole] = "showSection";
|
roles[ShowSectionRole] = "showSection";
|
||||||
roles[ReadMarkersRole] = "readMarkers";
|
roles[ReadMarkersRole] = "readMarkers";
|
||||||
roles[ExcessReadMarkersRole] = "excessReadMarkers";
|
|
||||||
roles[ReadMarkersStringRole] = "readMarkersString";
|
|
||||||
roles[ShowReadMarkersRole] = "showReadMarkers";
|
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||||
roles[ReactionRole] = "reaction";
|
roles[ReactionRole] = "reaction";
|
||||||
roles[ShowReactionsRole] = "showReactions";
|
roles[ShowReactionsRole] = "showReactions";
|
||||||
@@ -84,12 +84,21 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
beginResetModel();
|
|
||||||
if (m_currentRoom) {
|
if (m_currentRoom) {
|
||||||
|
// HACK: Reset the model to a null room first to make sure QML dismantles
|
||||||
|
// last room's objects before the room is actually changed
|
||||||
|
beginResetModel();
|
||||||
|
m_readMarkerModels.clear();
|
||||||
m_currentRoom->disconnect(this);
|
m_currentRoom->disconnect(this);
|
||||||
m_reactionModels.clear();
|
m_currentRoom = nullptr;
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
// Don't clear the member objects until the model has been fully reset and all
|
||||||
|
// refs cleared.
|
||||||
|
m_memberObjects.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
m_currentRoom = room;
|
m_currentRoom = room;
|
||||||
Q_EMIT roomChanged();
|
Q_EMIT roomChanged();
|
||||||
if (room) {
|
if (room) {
|
||||||
@@ -97,9 +106,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
room->setDisplayed();
|
room->setDisplayed();
|
||||||
|
|
||||||
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
|
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
|
||||||
if (const auto &roomMessageEvent = &*event->viewAs<RoomMessageEvent>()) {
|
createEventObjects(&*event->viewAs<RoomEvent>());
|
||||||
createEventObjects(roomMessageEvent);
|
|
||||||
}
|
|
||||||
if (event->event()->is<PollStartEvent>()) {
|
if (event->event()->is<PollStartEvent>()) {
|
||||||
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event->event()));
|
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event->event()));
|
||||||
}
|
}
|
||||||
@@ -112,11 +119,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
|
|
||||||
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
|
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
|
||||||
for (auto &&event : events) {
|
for (auto &&event : events) {
|
||||||
const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
createEventObjects(event.get());
|
||||||
|
|
||||||
if (message != nullptr) {
|
|
||||||
createEventObjects(message);
|
|
||||||
}
|
|
||||||
if (event->is<PollStartEvent>()) {
|
if (event->is<PollStartEvent>()) {
|
||||||
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get()));
|
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get()));
|
||||||
}
|
}
|
||||||
@@ -126,9 +129,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
|
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
|
||||||
for (auto &event : events) {
|
for (auto &event : events) {
|
||||||
if (const auto &roomMessageEvent = dynamic_cast<RoomMessageEvent *>(event.get())) {
|
createEventObjects(event.get());
|
||||||
createEventObjects(roomMessageEvent);
|
|
||||||
}
|
|
||||||
if (event->is<PollStartEvent>()) {
|
if (event->is<PollStartEvent>()) {
|
||||||
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get()));
|
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(event.get()));
|
||||||
}
|
}
|
||||||
@@ -149,14 +150,15 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
}
|
}
|
||||||
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
if (biggest < m_currentRoom->maxTimelineIndex()) {
|
||||||
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
|
auto rowBelowInserted = m_currentRoom->maxTimelineIndex() - biggest + timelineBaseIndex() - 1;
|
||||||
refreshEventRoles(rowBelowInserted, {MessageFilterModel::ShowAuthorRole});
|
refreshEventRoles(rowBelowInserted, {ContentModelRole});
|
||||||
}
|
}
|
||||||
for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) {
|
for (auto i = m_currentRoom->maxTimelineIndex() - biggest; i <= m_currentRoom->maxTimelineIndex() - lowest; ++i) {
|
||||||
refreshLastUserEvents(i);
|
refreshLastUserEvents(i);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] {
|
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
||||||
m_initialized = true;
|
m_initialized = true;
|
||||||
|
createEventObjects(event);
|
||||||
beginInsertRows({}, 0, 0);
|
beginInsertRows({}, 0, 0);
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
||||||
@@ -176,13 +178,13 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
endMoveRows();
|
endMoveRows();
|
||||||
movingEvent = false;
|
movingEvent = false;
|
||||||
}
|
}
|
||||||
refreshRow(timelineBaseIndex()); // Refresh the looks
|
fullEventRefresh(timelineBaseIndex());
|
||||||
refreshLastUserEvents(0);
|
refreshLastUserEvents(0);
|
||||||
if (timelineBaseIndex() > 0) { // Refresh below, see #312
|
if (timelineBaseIndex() > 0) { // Refresh below, see #312
|
||||||
refreshEventRoles(timelineBaseIndex() - 1, {MessageFilterModel::ShowAuthorRole});
|
refreshEventRoles(timelineBaseIndex() - 1, {ContentModelRole});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::refreshRow);
|
connect(m_currentRoom, &Room::pendingEventChanged, this, &MessageEventModel::fullEventRefresh);
|
||||||
connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this, [this](int i) {
|
connect(m_currentRoom, &Room::pendingEventAboutToDiscard, this, [this](int i) {
|
||||||
beginRemoveRows({}, i, i);
|
beginRemoveRows({}, i, i);
|
||||||
});
|
});
|
||||||
@@ -192,10 +194,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
moveReadMarker(toEventId);
|
moveReadMarker(toEventId);
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
|
||||||
const RoomMessageEvent *message = eventCast<const RoomMessageEvent>(newEvent);
|
createEventObjects(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?
|
||||||
@@ -203,25 +202,26 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
}
|
}
|
||||||
const auto eventIt = m_currentRoom->findInTimeline(eventId);
|
const auto eventIt = m_currentRoom->findInTimeline(eventId);
|
||||||
if (eventIt != m_currentRoom->historyEdge()) {
|
if (eventIt != m_currentRoom->historyEdge()) {
|
||||||
if (const auto &event = dynamic_cast<const RoomMessageEvent *>(&**eventIt)) {
|
createEventObjects(eventIt->event());
|
||||||
createEventObjects(event);
|
|
||||||
}
|
|
||||||
if (eventIt->event()->is<PollStartEvent>()) {
|
if (eventIt->event()->is<PollStartEvent>()) {
|
||||||
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(eventIt->event()));
|
m_currentRoom->createPollHandler(eventCast<const PollStartEvent>(eventIt->event()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
refreshEventRoles(eventId, {Qt::DisplayRole});
|
refreshEventRoles(eventId, {Qt::DisplayRole});
|
||||||
});
|
});
|
||||||
connect(m_currentRoom, &Room::changed, this, [this]() {
|
connect(m_currentRoom, &Room::changed, this, [this](Room::Changes changes) {
|
||||||
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
if (changes.testFlag(Quotient::Room::Change::Other)) {
|
||||||
auto event = it->event();
|
// this is slow
|
||||||
refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole, ExcessReadMarkersRole});
|
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||||
|
createEventObjects(it->event());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [this] {
|
connect(m_currentRoom->connection(), &Connection::ignoredUsersListChanged, this, [this] {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
endResetModel();
|
endResetModel();
|
||||||
});
|
});
|
||||||
|
|
||||||
qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localMember().id();
|
qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localMember().id();
|
||||||
} else {
|
} else {
|
||||||
lastReadEventId.clear();
|
lastReadEventId.clear();
|
||||||
@@ -235,14 +235,15 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::refreshEvent(const QString &eventId)
|
void MessageEventModel::fullEventRefresh(int row)
|
||||||
{
|
{
|
||||||
return refreshEventRoles(eventId);
|
auto roles = roleNames().keys();
|
||||||
}
|
// The author of an event never changes so should only be updated when a member
|
||||||
|
// changed signal is emitted.
|
||||||
void MessageEventModel::refreshRow(int row)
|
// This also avoids any race conditions where a member is updating and this refresh
|
||||||
{
|
// tries to access a member event that has already been deleted.
|
||||||
refreshEventRoles(row);
|
roles.removeAll(AuthorRole);
|
||||||
|
refreshEventRoles(row, roles);
|
||||||
}
|
}
|
||||||
|
|
||||||
int MessageEventModel::timelineBaseIndex() const
|
int MessageEventModel::timelineBaseIndex() const
|
||||||
@@ -335,11 +336,11 @@ QDateTime MessageEventModel::makeMessageTimestamp(const Quotient::Room::rev_iter
|
|||||||
using Quotient::TimelineItem;
|
using Quotient::TimelineItem;
|
||||||
auto rit = std::find_if(baseIt, timeline.rend(), hasValidTimestamp);
|
auto rit = std::find_if(baseIt, timeline.rend(), hasValidTimestamp);
|
||||||
if (rit != timeline.rend()) {
|
if (rit != timeline.rend()) {
|
||||||
return {rit->event()->originTimestamp().date(), {0, 0}, Qt::LocalTime};
|
return {rit->event()->originTimestamp().date(), {0, 0}, QTimeZone::LocalTime};
|
||||||
};
|
};
|
||||||
auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp);
|
auto it = std::find_if(baseIt.base(), timeline.end(), hasValidTimestamp);
|
||||||
if (it != timeline.end()) {
|
if (it != timeline.end()) {
|
||||||
return {it->event()->originTimestamp().date(), {0, 0}, Qt::LocalTime};
|
return {it->event()->originTimestamp().date(), {0, 0}, QTimeZone::LocalTime};
|
||||||
};
|
};
|
||||||
|
|
||||||
// What kind of room is that?..
|
// What kind of room is that?..
|
||||||
@@ -358,8 +359,7 @@ void MessageEventModel::refreshLastUserEvents(int baseTimelineRow)
|
|||||||
const auto limit = timelineBottom + std::min(baseTimelineRow + 10, m_currentRoom->timelineSize());
|
const auto limit = timelineBottom + std::min(baseTimelineRow + 10, m_currentRoom->timelineSize());
|
||||||
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0); it != limit; ++it) {
|
for (auto it = timelineBottom + std::max(baseTimelineRow - 10, 0); it != limit; ++it) {
|
||||||
if ((*it)->senderId() == lastSender) {
|
if ((*it)->senderId() == lastSender) {
|
||||||
auto idx = index(it - timelineBottom);
|
fullEventRefresh(it - timelineBottom);
|
||||||
Q_EMIT dataChanged(idx, idx);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,6 +388,8 @@ void MessageEventModel::fetchMore(const QModelIndex &parent)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static NeochatRoomMember *emptyNeochatRoomMember = new NeochatRoomMember;
|
||||||
|
|
||||||
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||||
{
|
{
|
||||||
if (!checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
|
if (!checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
|
||||||
@@ -460,7 +462,18 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == AuthorRole) {
|
if (role == AuthorRole) {
|
||||||
return eventHandler.getAuthor(isPending);
|
QString mId;
|
||||||
|
if (isPending) {
|
||||||
|
mId = m_currentRoom->localMember().id();
|
||||||
|
} else {
|
||||||
|
mId = evt.senderId();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_memberObjects.contains(mId)) {
|
||||||
|
return QVariant::fromValue<NeochatRoomMember *>(emptyNeochatRoomMember);
|
||||||
|
}
|
||||||
|
|
||||||
|
return QVariant::fromValue<NeochatRoomMember *>(m_memberObjects.at(mId).get());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == HighlightRole) {
|
if (role == HighlightRole) {
|
||||||
@@ -491,11 +504,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
if (role == ProgressInfoRole) {
|
if (role == ProgressInfoRole) {
|
||||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||||
if (e->hasFileContent()) {
|
if (e->hasFileContent()) {
|
||||||
return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
|
return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (auto e = eventCast<const StickerEvent>(&evt)) {
|
if (eventCast<const StickerEvent>(&evt)) {
|
||||||
return QVariant::fromValue(m_currentRoom->fileTransferInfo(e->id()));
|
return QVariant::fromValue(m_currentRoom->cachedFileTransferInfo(&evt));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -504,11 +517,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
return eventHandler.getTime(isPending, lastUpdated);
|
return eventHandler.getTime(isPending, lastUpdated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == TimeStringRole) {
|
|
||||||
auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
|
|
||||||
return eventHandler.getTimeString(false, QLocale::ShortFormat, isPending, lastUpdated);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == SectionRole) {
|
if (role == SectionRole) {
|
||||||
auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
|
auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
|
||||||
return eventHandler.getTimeString(true, QLocale::ShortFormat, isPending, lastUpdated);
|
return eventHandler.getTimeString(true, QLocale::ShortFormat, isPending, lastUpdated);
|
||||||
@@ -539,19 +547,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (role == ReadMarkersRole) {
|
if (role == ReadMarkersRole) {
|
||||||
return eventHandler.getReadMarkers();
|
if (m_readMarkerModels.contains(evt.id())) {
|
||||||
}
|
return QVariant::fromValue<ReadMarkerModel *>(m_readMarkerModels[evt.id()].get());
|
||||||
|
} else {
|
||||||
if (role == ExcessReadMarkersRole) {
|
return QVariantList();
|
||||||
return eventHandler.getNumberExcessReadMarkers();
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (role == ReadMarkersStringRole) {
|
|
||||||
return eventHandler.getReadMarkersString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == ShowReadMarkersRole) {
|
if (role == ShowReadMarkersRole) {
|
||||||
return eventHandler.hasReadMarkers();
|
return m_readMarkerModels.contains(evt.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == ReactionRole) {
|
if (role == ReactionRole) {
|
||||||
@@ -612,30 +616,71 @@ int MessageEventModel::eventIdToRow(const QString &eventID) const
|
|||||||
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
return it - m_currentRoom->messageEvents().rbegin() + timelineBaseIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *event)
|
void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
|
||||||
{
|
{
|
||||||
auto eventId = event->id();
|
if (event == nullptr) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// ReactionModel handles updates to add and remove reactions, we only need to
|
auto eventId = event->id();
|
||||||
|
auto senderId = event->senderId();
|
||||||
|
// A pending event might not have a sender ID set yet but in that case it must
|
||||||
|
// be the local member.
|
||||||
|
if (senderId.isEmpty()) {
|
||||||
|
senderId = m_currentRoom->localMember().id();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_memberObjects.contains(senderId)) {
|
||||||
|
m_memberObjects[senderId] = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_currentRoom, senderId));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMarkerModel handles updates to add and remove markers, we only need to
|
||||||
// handle adding and removing whole models here.
|
// handle adding and removing whole models here.
|
||||||
if (m_reactionModels.contains(eventId)) {
|
if (m_readMarkerModels.contains(eventId)) {
|
||||||
// If a model already exists but now has no reactions remove it
|
// If a model already exists but now has no reactions remove it
|
||||||
if (m_reactionModels[eventId]->rowCount() <= 0) {
|
if (m_readMarkerModels[eventId]->rowCount() <= 0) {
|
||||||
m_reactionModels.remove(eventId);
|
m_readMarkerModels.remove(eventId);
|
||||||
if (!resetting) {
|
if (!resetting) {
|
||||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
refreshEventRoles(eventId, {ReadMarkersRole, ShowReadMarkersRole});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (m_currentRoom->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
|
auto memberIds = m_currentRoom->userIdsAtEvent(eventId);
|
||||||
|
memberIds.remove(m_currentRoom->localMember().id());
|
||||||
|
if (memberIds.size() > 0) {
|
||||||
// If a model doesn't exist and there are reactions add it.
|
// If a model doesn't exist and there are reactions add it.
|
||||||
auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(event, m_currentRoom));
|
auto newModel = QSharedPointer<ReadMarkerModel>(new ReadMarkerModel(eventId, m_currentRoom));
|
||||||
if (reactionModel->rowCount() > 0) {
|
if (newModel->rowCount() > 0) {
|
||||||
m_reactionModels[eventId] = reactionModel;
|
m_readMarkerModels[eventId] = newModel;
|
||||||
|
if (!resetting) {
|
||||||
|
refreshEventRoles(eventId, {ReadMarkersRole, ShowReadMarkersRole});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (const auto roomEvent = eventCast<const RoomMessageEvent>(event)) {
|
||||||
|
// 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) {
|
if (!resetting) {
|
||||||
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
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(roomEvent, m_currentRoom));
|
||||||
|
if (reactionModel->rowCount() > 0) {
|
||||||
|
m_reactionModels[eventId] = reactionModel;
|
||||||
|
if (!resetting) {
|
||||||
|
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
|
|
||||||
#include "linkpreviewer.h"
|
#include "linkpreviewer.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
#include "neochatroommember.h"
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
|
#include "readmarkermodel.h"
|
||||||
|
|
||||||
class ReactionModel;
|
class ReactionModel;
|
||||||
|
|
||||||
@@ -42,7 +44,6 @@ public:
|
|||||||
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||||
EventIdRole, /**< The matrix event ID of the event. */
|
EventIdRole, /**< The matrix event ID of the event. */
|
||||||
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
|
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
|
||||||
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. */
|
||||||
HighlightRole, /**< Whether the event should be highlighted. */
|
HighlightRole, /**< Whether the event should be highlighted. */
|
||||||
@@ -59,8 +60,6 @@ public:
|
|||||||
ShowSectionRole, /**< Whether the section header should be shown. */
|
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||||
|
|
||||||
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
|
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
|
||||||
ExcessReadMarkersRole, /**< The number of other users at the event after the first 5. */
|
|
||||||
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
|
|
||||||
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||||
ReactionRole, /**< List model for this event. */
|
ReactionRole, /**< List model for this event. */
|
||||||
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
|
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
|
||||||
@@ -108,10 +107,6 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
bool event(QEvent *event) override;
|
bool event(QEvent *event) override;
|
||||||
|
|
||||||
private Q_SLOTS:
|
|
||||||
int refreshEvent(const QString &eventId);
|
|
||||||
void refreshRow(int row);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QPointer<NeoChatRoom> m_currentRoom = nullptr;
|
QPointer<NeoChatRoom> m_currentRoom = nullptr;
|
||||||
QString lastReadEventId;
|
QString lastReadEventId;
|
||||||
@@ -121,6 +116,8 @@ private:
|
|||||||
bool movingEvent = false;
|
bool movingEvent = false;
|
||||||
KFormat m_format;
|
KFormat m_format;
|
||||||
|
|
||||||
|
std::map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
|
||||||
|
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
|
||||||
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
|
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
|
||||||
|
|
||||||
[[nodiscard]] int timelineBaseIndex() const;
|
[[nodiscard]] int timelineBaseIndex() const;
|
||||||
@@ -129,12 +126,13 @@ private:
|
|||||||
bool canFetchMore(const QModelIndex &parent) const override;
|
bool canFetchMore(const QModelIndex &parent) const override;
|
||||||
void fetchMore(const QModelIndex &parent) override;
|
void fetchMore(const QModelIndex &parent) override;
|
||||||
|
|
||||||
|
void fullEventRefresh(int row);
|
||||||
void refreshLastUserEvents(int baseTimelineRow);
|
void refreshLastUserEvents(int baseTimelineRow);
|
||||||
void refreshEventRoles(int row, const QList<int> &roles = {});
|
void refreshEventRoles(int row, const QList<int> &roles = {});
|
||||||
int refreshEventRoles(const QString &eventId, const QList<int> &roles = {});
|
int refreshEventRoles(const QString &eventId, const QList<int> &roles = {});
|
||||||
void moveReadMarker(const QString &toEventId);
|
void moveReadMarker(const QString &toEventId);
|
||||||
|
|
||||||
void createEventObjects(const Quotient::RoomMessageEvent *event);
|
void createEventObjects(const Quotient::RoomEvent *event);
|
||||||
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
|
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
|
||||||
bool m_initialized = false;
|
bool m_initialized = false;
|
||||||
|
|
||||||
|
|||||||
@@ -4,10 +4,13 @@
|
|||||||
#include "messagefiltermodel.h"
|
#include "messagefiltermodel.h"
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
#include <QVariant>
|
||||||
|
|
||||||
#include "enums/delegatetype.h"
|
#include "enums/delegatetype.h"
|
||||||
|
#include "messagecontentmodel.h"
|
||||||
#include "messageeventmodel.h"
|
#include "messageeventmodel.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
|
#include "neochatroommember.h"
|
||||||
#include "timelinemodel.h"
|
#include "timelinemodel.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -91,22 +94,12 @@ QVariant MessageFilterModel::data(const QModelIndex &index, int role) const
|
|||||||
return authorList(mapToSource(index).row());
|
return authorList(mapToSource(index).row());
|
||||||
} else if (role == ExcessAuthorsRole) {
|
} else if (role == ExcessAuthorsRole) {
|
||||||
return excessAuthors(mapToSource(index).row());
|
return excessAuthors(mapToSource(index).row());
|
||||||
} else if (role == ShowAuthorRole) {
|
} else if (role == MessageEventModel::ContentModelRole) {
|
||||||
for (auto r = index.row() + 1; r < rowCount(); ++r) {
|
const auto model = qvariant_cast<MessageContentModel *>(mapToSource(index).data(MessageEventModel::ContentModelRole));
|
||||||
auto i = this->index(r, 0);
|
if (model != nullptr && !showAuthor(index)) {
|
||||||
// Note !itemData(i).empty() is a check for instances where rows have been removed, e.g. when the read marker is moved.
|
model->setShowAuthor(false);
|
||||||
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
|
|
||||||
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
|
|
||||||
if (data(i, MessageEventModel::SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
|
||||||
return data(i, MessageEventModel::AuthorRole) != data(index, MessageEventModel::AuthorRole)
|
|
||||||
|| data(i, MessageEventModel::DelegateTypeRole) == DelegateType::State
|
|
||||||
|| data(i, MessageEventModel::TimeRole).toDateTime().msecsTo(data(index, MessageEventModel::TimeRole).toDateTime()) > 600000
|
|
||||||
|| data(i, MessageEventModel::TimeRole).toDateTime().toLocalTime().date().day()
|
|
||||||
!= data(index, MessageEventModel::TimeRole).toDateTime().toLocalTime().date().day();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return QVariant::fromValue<MessageContentModel *>(model);
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
return QSortFilterProxyModel::data(index, role);
|
return QSortFilterProxyModel::data(index, role);
|
||||||
}
|
}
|
||||||
@@ -118,10 +111,28 @@ QHash<int, QByteArray> MessageFilterModel::roleNames() const
|
|||||||
roles[StateEventsRole] = "stateEvents";
|
roles[StateEventsRole] = "stateEvents";
|
||||||
roles[AuthorListRole] = "authorList";
|
roles[AuthorListRole] = "authorList";
|
||||||
roles[ExcessAuthorsRole] = "excessAuthors";
|
roles[ExcessAuthorsRole] = "excessAuthors";
|
||||||
roles[ShowAuthorRole] = "showAuthor";
|
|
||||||
return roles;
|
return roles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool MessageFilterModel::showAuthor(QModelIndex index) const
|
||||||
|
{
|
||||||
|
for (auto r = index.row() + 1; r < rowCount(); ++r) {
|
||||||
|
auto i = this->index(r, 0);
|
||||||
|
// Note !itemData(i).empty() is a check for instances where rows have been removed, e.g. when the read marker is moved.
|
||||||
|
// While the row is removed the subsequent row indexes are not changed so we need to skip over the removed index.
|
||||||
|
// See - https://doc.qt.io/qt-5/qabstractitemmodel.html#beginRemoveRows
|
||||||
|
if (data(i, MessageEventModel::SpecialMarksRole) != EventStatus::Hidden && !itemData(i).empty()) {
|
||||||
|
return data(i, MessageEventModel::AuthorRole) != data(index, MessageEventModel::AuthorRole)
|
||||||
|
|| data(i, MessageEventModel::DelegateTypeRole) == DelegateType::State
|
||||||
|
|| data(i, MessageEventModel::TimeRole).toDateTime().msecsTo(data(index, MessageEventModel::TimeRole).toDateTime()) > 600000
|
||||||
|
|| data(i, MessageEventModel::TimeRole).toDateTime().toLocalTime().date().day()
|
||||||
|
!= data(index, MessageEventModel::TimeRole).toDateTime().toLocalTime().date().day();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
QString MessageFilterModel::aggregateEventToString(int sourceRow) const
|
||||||
{
|
{
|
||||||
QStringList parts;
|
QStringList parts;
|
||||||
|
|||||||
@@ -34,7 +34,6 @@ public:
|
|||||||
StateEventsRole, /**< List of state events in the aggregated state. */
|
StateEventsRole, /**< List of state events in the aggregated state. */
|
||||||
AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */
|
AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */
|
||||||
ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */
|
ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */
|
||||||
ShowAuthorRole, /**< Whether the author (name and avatar) should be shown at this message. */
|
|
||||||
LastRole, // Keep this last
|
LastRole, // Keep this last
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -62,6 +61,8 @@ public:
|
|||||||
private:
|
private:
|
||||||
bool eventIsVisible(int sourceRow, const QModelIndex &sourceParent) const;
|
bool eventIsVisible(int sourceRow, const QModelIndex &sourceParent) const;
|
||||||
|
|
||||||
|
bool showAuthor(QModelIndex index) const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Aggregation of the text of consecutive state events starting at row.
|
* @brief Aggregation of the text of consecutive state events starting at row.
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ void PushRuleModel::setRules(QList<Quotient::PushRule> rules, PushRuleKind::Kind
|
|||||||
for (const auto &rule : rules) {
|
for (const auto &rule : rules) {
|
||||||
QString roomId;
|
QString roomId;
|
||||||
if (rule.conditions.size() > 0) {
|
if (rule.conditions.size() > 0) {
|
||||||
for (const auto &condition : rule.conditions) {
|
for (const auto &condition : std::as_const(rule.conditions)) {
|
||||||
if (condition.key == QStringLiteral("room_id")) {
|
if (condition.key == QStringLiteral("room_id")) {
|
||||||
roomId = condition.pattern;
|
roomId = condition.pattern;
|
||||||
}
|
}
|
||||||
@@ -163,7 +163,7 @@ PushRuleSection::Section PushRuleModel::getSection(Quotient::PushRule rule)
|
|||||||
}
|
}
|
||||||
// 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 (const auto &condition : std::as_const(rule.conditions)) {
|
||||||
if (condition.key == QStringLiteral("room_id")) {
|
if (condition.key == QStringLiteral("room_id")) {
|
||||||
return PushRuleSection::RoomKeywords;
|
return PushRuleSection::RoomKeywords;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
#include <Quotient/user.h>
|
#include <Quotient/roommember.h>
|
||||||
|
|
||||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
|
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
|
||||||
: QAbstractListModel(nullptr)
|
: QAbstractListModel(nullptr)
|
||||||
@@ -69,8 +69,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
|||||||
text += i18nc("Separate the usernames of users", " and ");
|
text += i18nc("Separate the usernames of users", " and ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
auto displayName = reaction.authors.at(i).toMap()[QStringLiteral("displayName")].toString();
|
text += m_room->member(reaction.authors.at(i)).displayName();
|
||||||
text += displayName.isEmpty() ? reaction.authors.at(i).toMap()[QStringLiteral("id")].toString() : displayName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reaction.authors.count() > 3) {
|
if (reaction.authors.count() > 3) {
|
||||||
@@ -86,13 +85,9 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
|||||||
return text;
|
return text;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (role == AuthorsRole) {
|
if (role == HasLocalMember) {
|
||||||
return reaction.authors;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (role == HasLocalUser) {
|
|
||||||
for (auto author : reaction.authors) {
|
for (auto author : reaction.authors) {
|
||||||
if (author.toMap()[QStringLiteral("id")] == m_room->localMember().id()) {
|
if (author == m_room->localMember().id()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -121,13 +116,13 @@ void ReactionModel::updateReactions()
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
QMap<QString, QList<Quotient::RoomMember>> reactions = {};
|
QMap<QString, QStringList> reactions = {};
|
||||||
for (const auto &a : annotations) {
|
for (const auto &a : annotations) {
|
||||||
if (a->isRedacted()) { // Just in case?
|
if (a->isRedacted()) { // Just in case?
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (const auto &e = eventCast<const Quotient::ReactionEvent>(a)) {
|
if (const auto &e = eventCast<const Quotient::ReactionEvent>(a)) {
|
||||||
reactions[e->key()].append(m_room->member(e->senderId()));
|
reactions[e->key()].append(e->senderId());
|
||||||
if (e->contentJson()[QStringLiteral("shortcode")].toString().length()) {
|
if (e->contentJson()[QStringLiteral("shortcode")].toString().length()) {
|
||||||
m_shortcodes[e->key()] = e->contentJson()[QStringLiteral("shortcode")].toString().toHtmlEscaped();
|
m_shortcodes[e->key()] = e->contentJson()[QStringLiteral("shortcode")].toString().toHtmlEscaped();
|
||||||
}
|
}
|
||||||
@@ -138,15 +133,14 @@ void ReactionModel::updateReactions()
|
|||||||
endResetModel();
|
endResetModel();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto i = reactions.constBegin();
|
auto i = reactions.constBegin();
|
||||||
while (i != reactions.constEnd()) {
|
while (i != reactions.constEnd()) {
|
||||||
QVariantList authors;
|
QStringList members;
|
||||||
for (const auto &author : i.value()) {
|
for (const auto &member : i.value()) {
|
||||||
authors.append(m_room->getUser(author));
|
members.append(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_reactions.append(ReactionModel::Reaction{i.key(), authors});
|
m_reactions.append(ReactionModel::Reaction{i.key(), members});
|
||||||
++i;
|
++i;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,8 +153,7 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
|||||||
{TextContentRole, "textContent"},
|
{TextContentRole, "textContent"},
|
||||||
{ReactionRole, "reaction"},
|
{ReactionRole, "reaction"},
|
||||||
{ToolTipRole, "toolTip"},
|
{ToolTipRole, "toolTip"},
|
||||||
{AuthorsRole, "authors"},
|
{HasLocalMember, "hasLocalMember"},
|
||||||
{HasLocalUser, "hasLocalUser"},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,13 +5,8 @@
|
|||||||
|
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include <QAbstractListModel>
|
#include <QAbstractListModel>
|
||||||
#include <QQmlEngine>
|
|
||||||
#include <Quotient/events/reactionevent.h>
|
#include <Quotient/events/reactionevent.h>
|
||||||
|
#include <Quotient/roommember.h>
|
||||||
namespace Quotient
|
|
||||||
{
|
|
||||||
class User;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class ReactionModel
|
* @class ReactionModel
|
||||||
@@ -30,7 +25,7 @@ public:
|
|||||||
*/
|
*/
|
||||||
struct Reaction {
|
struct Reaction {
|
||||||
QString reaction; /**< The reaction emoji. */
|
QString reaction; /**< The reaction emoji. */
|
||||||
QVariantList authors; /**< The list of authors who sent the given reaction. */
|
QStringList authors; /**< The list of authors who sent the given reaction. */
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -40,8 +35,7 @@ public:
|
|||||||
TextContentRole = Qt::DisplayRole, /**< The text to show in the reaction. */
|
TextContentRole = Qt::DisplayRole, /**< The text to show in the reaction. */
|
||||||
ReactionRole, /**< The reaction emoji. */
|
ReactionRole, /**< The reaction emoji. */
|
||||||
ToolTipRole, /**< The tool tip to show for the reaction. */
|
ToolTipRole, /**< The tool tip to show for the reaction. */
|
||||||
AuthorsRole, /**< The list of authors who sent the given reaction. */
|
HasLocalMember, /**< Whether the local member is in the list of authors. */
|
||||||
HasLocalUser, /**< Whether the local user is in the list of authors. */
|
|
||||||
};
|
};
|
||||||
|
|
||||||
explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room);
|
explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room);
|
||||||
|
|||||||
136
src/models/readmarkermodel.cpp
Normal file
136
src/models/readmarkermodel.cpp
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 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 "readmarkermodel.h"
|
||||||
|
|
||||||
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
#include <Quotient/roommember.h>
|
||||||
|
|
||||||
|
#define MAXMARKERS 5
|
||||||
|
|
||||||
|
ReadMarkerModel::ReadMarkerModel(const QString &eventId, NeoChatRoom *room)
|
||||||
|
: QAbstractListModel(nullptr)
|
||||||
|
, m_room(room)
|
||||||
|
, m_eventId(eventId)
|
||||||
|
{
|
||||||
|
Q_ASSERT(!m_eventId.isEmpty());
|
||||||
|
Q_ASSERT(m_room != nullptr);
|
||||||
|
|
||||||
|
connect(m_room, &NeoChatRoom::changed, this, [this](Quotient::Room::Changes changes) {
|
||||||
|
if (m_room != nullptr && changes.testFlag(Quotient::Room::Change::Other)) {
|
||||||
|
auto memberIds = m_room->userIdsAtEvent(m_eventId).values();
|
||||||
|
if (memberIds == m_markerIds) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
m_markerIds.clear();
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
memberIds.removeAll(m_room->localMember().id());
|
||||||
|
m_markerIds = memberIds;
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
Q_EMIT reactionUpdated();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_room, &NeoChatRoom::memberNameUpdated, this, [this](Quotient::RoomMember member) {
|
||||||
|
if (m_markerIds.contains(member.id())) {
|
||||||
|
const auto memberIndex = index(m_markerIds.indexOf(member.id()));
|
||||||
|
Q_EMIT dataChanged(memberIndex, memberIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
connect(m_room, &NeoChatRoom::memberAvatarUpdated, this, [this](Quotient::RoomMember member) {
|
||||||
|
if (m_markerIds.contains(member.id())) {
|
||||||
|
const auto memberIndex = index(m_markerIds.indexOf(member.id()));
|
||||||
|
Q_EMIT dataChanged(memberIndex, memberIndex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
beginResetModel();
|
||||||
|
auto userIds = m_room->userIdsAtEvent(m_eventId);
|
||||||
|
userIds.remove(m_room->localMember().id());
|
||||||
|
m_markerIds = userIds.values();
|
||||||
|
endResetModel();
|
||||||
|
|
||||||
|
Q_EMIT reactionUpdated();
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariant ReadMarkerModel::data(const QModelIndex &index, int role) const
|
||||||
|
{
|
||||||
|
if (!index.isValid()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (index.row() >= rowCount()) {
|
||||||
|
qDebug() << "ReactionModel, something's wrong: index.row() >= rowCount()";
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto member = m_room->member(m_markerIds.value(index.row()));
|
||||||
|
|
||||||
|
if (role == DisplayNameRole) {
|
||||||
|
return member.htmlSafeDisplayName();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == AvatarUrlRole) {
|
||||||
|
return member.avatarUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (role == ColorRole) {
|
||||||
|
return member.color();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
int ReadMarkerModel::rowCount(const QModelIndex &parent) const
|
||||||
|
{
|
||||||
|
Q_UNUSED(parent)
|
||||||
|
return std::min(int(m_markerIds.size()), MAXMARKERS);
|
||||||
|
}
|
||||||
|
|
||||||
|
QHash<int, QByteArray> ReadMarkerModel::roleNames() const
|
||||||
|
{
|
||||||
|
return {
|
||||||
|
{DisplayNameRole, "displayName"},
|
||||||
|
{AvatarUrlRole, "avatarUrl"},
|
||||||
|
{ColorRole, "memberColor"},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ReadMarkerModel::readMarkersString()
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* The string ends up in the form
|
||||||
|
* "x users: user1DisplayName, user2DisplayName, etc."
|
||||||
|
*/
|
||||||
|
QString readMarkersString = i18np("1 user: ", "%1 users: ", m_markerIds.size());
|
||||||
|
for (const auto &memberId : m_markerIds) {
|
||||||
|
auto member = m_room->member(memberId);
|
||||||
|
QString displayName = member.htmlSafeDisambiguatedName();
|
||||||
|
if (displayName.isEmpty()) {
|
||||||
|
displayName = i18nc("A member who is not in the room has been requested.", "unknown member");
|
||||||
|
}
|
||||||
|
readMarkersString += displayName + i18nc("list separator", ", ");
|
||||||
|
}
|
||||||
|
readMarkersString.chop(2);
|
||||||
|
return readMarkersString;
|
||||||
|
}
|
||||||
|
|
||||||
|
QString ReadMarkerModel::excessReadMarkersString()
|
||||||
|
{
|
||||||
|
if (m_room == nullptr) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (m_markerIds.size() > MAXMARKERS) {
|
||||||
|
return QStringLiteral("+ ") + QString::number(m_markerIds.size() - MAXMARKERS);
|
||||||
|
} else {
|
||||||
|
return QString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#include "moc_readmarkermodel.cpp"
|
||||||
79
src/models/readmarkermodel.h
Normal file
79
src/models/readmarkermodel.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2024 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 <QAbstractListModel>
|
||||||
|
#include <qtmetamacros.h>
|
||||||
|
|
||||||
|
#include "neochatroom.h"
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class ReadMarkerModel
|
||||||
|
*
|
||||||
|
* This class defines the model for visualising a list of reactions to an event.
|
||||||
|
*/
|
||||||
|
class ReadMarkerModel : public QAbstractListModel
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
QML_ELEMENT
|
||||||
|
QML_UNCREATABLE("")
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a string with the names of the read markers at the event.
|
||||||
|
*
|
||||||
|
* This is in the form "x users: name 1, name 2, ...".
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString readMarkersString READ readMarkersString NOTIFY reactionUpdated)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns the number of excess user read markers for the event.
|
||||||
|
*
|
||||||
|
* This returns a string in the form "+ x" ready for use in the UI.
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(QString excessReadMarkersString READ excessReadMarkersString NOTIFY reactionUpdated)
|
||||||
|
|
||||||
|
public:
|
||||||
|
/**
|
||||||
|
* @brief Defines the model roles.
|
||||||
|
*/
|
||||||
|
enum Roles {
|
||||||
|
DisplayNameRole = Qt::DisplayRole, /**< The display name of the member in the room. */
|
||||||
|
AvatarUrlRole, /**< The avatar for the member in the room. */
|
||||||
|
ColorRole, /**< The color for the member. */
|
||||||
|
};
|
||||||
|
|
||||||
|
explicit ReadMarkerModel(const QString &eventId, NeoChatRoom *room);
|
||||||
|
|
||||||
|
QString readMarkersString();
|
||||||
|
QString excessReadMarkersString();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the given role value at the given index.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::data
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Number of rows in the model.
|
||||||
|
*
|
||||||
|
* @sa QAbstractItemModel::rowCount
|
||||||
|
*/
|
||||||
|
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Returns a mapping from Role enum values to role names.
|
||||||
|
*
|
||||||
|
* @sa Roles, QAbstractItemModel::roleNames()
|
||||||
|
*/
|
||||||
|
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void reactionUpdated();
|
||||||
|
|
||||||
|
private:
|
||||||
|
QPointer<NeoChatRoom> m_room;
|
||||||
|
QString m_eventId;
|
||||||
|
QList<QString> m_markerIds;
|
||||||
|
};
|
||||||
@@ -221,6 +221,10 @@ int RoomTreeModel::rowCount(const QModelIndex &parent) const
|
|||||||
parentItem = static_cast<RoomTreeItem *>(parent.internalPointer());
|
parentItem = static_cast<RoomTreeItem *>(parent.internalPointer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!parentItem) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
return parentItem->childCount();
|
return parentItem->childCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include "eventhandler.h"
|
#include "eventhandler.h"
|
||||||
#include "models/messagecontentmodel.h"
|
#include "models/messagecontentmodel.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
#include "neochatroommember.h"
|
||||||
|
|
||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
|
|
||||||
@@ -44,7 +45,7 @@ void SearchModel::search()
|
|||||||
}
|
}
|
||||||
|
|
||||||
RoomEventFilter filter;
|
RoomEventFilter filter;
|
||||||
filter.unreadThreadNotifications = {};
|
filter.unreadThreadNotifications = std::nullopt;
|
||||||
filter.lazyLoadMembers = true;
|
filter.lazyLoadMembers = true;
|
||||||
filter.includeRedundantMembers = false;
|
filter.includeRedundantMembers = false;
|
||||||
filter.notRooms = QStringList();
|
filter.notRooms = QStringList();
|
||||||
@@ -58,7 +59,7 @@ void SearchModel::search()
|
|||||||
.orderBy = "recent"_ls,
|
.orderBy = "recent"_ls,
|
||||||
.eventContext = SearchJob::IncludeEventContext{3, 3, true},
|
.eventContext = SearchJob::IncludeEventContext{3, 3, true},
|
||||||
.includeState = false,
|
.includeState = false,
|
||||||
.groupings = {},
|
.groupings = std::nullopt,
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -66,7 +67,17 @@ void SearchModel::search()
|
|||||||
m_job = job;
|
m_job = job;
|
||||||
connect(job, &BaseJob::finished, this, [this, job] {
|
connect(job, &BaseJob::finished, this, [this, job] {
|
||||||
beginResetModel();
|
beginResetModel();
|
||||||
|
m_memberObjects.clear();
|
||||||
m_result = job->searchCategories().roomEvents;
|
m_result = job->searchCategories().roomEvents;
|
||||||
|
|
||||||
|
if (m_result.has_value()) {
|
||||||
|
for (const auto &result : m_result.value().results) {
|
||||||
|
if (!m_memberObjects.contains(result.result->senderId())) {
|
||||||
|
m_memberObjects[result.result->senderId()] = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_room, result.result->senderId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
endResetModel();
|
endResetModel();
|
||||||
setSearching(false);
|
setSearching(false);
|
||||||
m_job = nullptr;
|
m_job = nullptr;
|
||||||
@@ -82,10 +93,8 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
|||||||
EventHandler eventHandler(m_room, &event);
|
EventHandler eventHandler(m_room, &event);
|
||||||
|
|
||||||
switch (role) {
|
switch (role) {
|
||||||
case ShowAuthorRole:
|
|
||||||
return true;
|
|
||||||
case AuthorRole:
|
case AuthorRole:
|
||||||
return eventHandler.getAuthor();
|
return QVariant::fromValue<NeochatRoomMember *>(m_memberObjects.at(event.senderId()).get());
|
||||||
case ShowSectionRole:
|
case ShowSectionRole:
|
||||||
if (row == 0) {
|
if (row == 0) {
|
||||||
return true;
|
return true;
|
||||||
@@ -93,10 +102,6 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
|||||||
return event.originTimestamp().date() != m_result->results[row - 1].result->originTimestamp().date();
|
return event.originTimestamp().date() != m_result->results[row - 1].result->originTimestamp().date();
|
||||||
case SectionRole:
|
case SectionRole:
|
||||||
return eventHandler.getTimeString(true);
|
return eventHandler.getTimeString(true);
|
||||||
case TimeRole:
|
|
||||||
return eventHandler.getTime();
|
|
||||||
case TimeStringRole:
|
|
||||||
return eventHandler.getTimeString(false);
|
|
||||||
case ShowReactionsRole:
|
case ShowReactionsRole:
|
||||||
return false;
|
return false;
|
||||||
case ShowReadMarkersRole:
|
case ShowReadMarkersRole:
|
||||||
@@ -145,9 +150,6 @@ QHash<int, QByteArray> SearchModel::roleNames() const
|
|||||||
{AuthorRole, "author"},
|
{AuthorRole, "author"},
|
||||||
{ShowSectionRole, "showSection"},
|
{ShowSectionRole, "showSection"},
|
||||||
{SectionRole, "section"},
|
{SectionRole, "section"},
|
||||||
{TimeRole, "time"},
|
|
||||||
{TimeStringRole, "timeString"},
|
|
||||||
{ShowAuthorRole, "showAuthor"},
|
|
||||||
{EventIdRole, "eventId"},
|
{EventIdRole, "eventId"},
|
||||||
{ExcessReadMarkersRole, "excessReadMarkers"},
|
{ExcessReadMarkersRole, "excessReadMarkers"},
|
||||||
{HighlightRole, "isHighlighted"},
|
{HighlightRole, "isHighlighted"},
|
||||||
|
|||||||
@@ -9,6 +9,8 @@
|
|||||||
|
|
||||||
#include <Quotient/csapi/search.h>
|
#include <Quotient/csapi/search.h>
|
||||||
|
|
||||||
|
#include "neochatroommember.h"
|
||||||
|
|
||||||
namespace Quotient
|
namespace Quotient
|
||||||
{
|
{
|
||||||
class Connection;
|
class Connection;
|
||||||
@@ -52,12 +54,9 @@ public:
|
|||||||
*/
|
*/
|
||||||
enum Roles {
|
enum Roles {
|
||||||
DelegateTypeRole = Qt::DisplayRole + 1,
|
DelegateTypeRole = Qt::DisplayRole + 1,
|
||||||
ShowAuthorRole,
|
|
||||||
AuthorRole,
|
AuthorRole,
|
||||||
ShowSectionRole,
|
ShowSectionRole,
|
||||||
SectionRole,
|
SectionRole,
|
||||||
TimeRole,
|
|
||||||
TimeStringRole,
|
|
||||||
EventIdRole,
|
EventIdRole,
|
||||||
ExcessReadMarkersRole,
|
ExcessReadMarkersRole,
|
||||||
HighlightRole,
|
HighlightRole,
|
||||||
@@ -126,4 +125,6 @@ private:
|
|||||||
std::optional<Quotient::SearchJob::ResultRoomEvents> m_result = std::nullopt;
|
std::optional<Quotient::SearchJob::ResultRoomEvents> m_result = std::nullopt;
|
||||||
Quotient::SearchJob *m_job = nullptr;
|
Quotient::SearchJob *m_job = nullptr;
|
||||||
bool m_searching = false;
|
bool m_searching = false;
|
||||||
|
|
||||||
|
std::map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
#include <Quotient/jobs/basejob.h>
|
#include <Quotient/jobs/basejob.h>
|
||||||
#include <Quotient/room.h>
|
#include <Quotient/room.h>
|
||||||
|
|
||||||
#include "definitions.h"
|
|
||||||
#include "neochatconnection.h"
|
#include "neochatconnection.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
|
|
||||||
@@ -88,7 +87,7 @@ void SpaceChildrenModel::refreshModel()
|
|||||||
m_rootItem =
|
m_rootItem =
|
||||||
new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()), nullptr, m_space->id(), m_space->displayName(), m_space->canonicalAlias());
|
new SpaceTreeItem(dynamic_cast<NeoChatConnection *>(m_space->connection()), nullptr, m_space->id(), m_space->displayName(), m_space->canonicalAlias());
|
||||||
endResetModel();
|
endResetModel();
|
||||||
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(m_space->id(), quotientNone, quotientNone, 1);
|
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(m_space->id(), std::nullopt, std::nullopt, 1);
|
||||||
m_currentJobs.append(job);
|
m_currentJobs.append(job);
|
||||||
connect(job, &Quotient::BaseJob::success, this, [this, job]() {
|
connect(job, &Quotient::BaseJob::success, this, [this, job]() {
|
||||||
insertChildren(job->rooms());
|
insertChildren(job->rooms());
|
||||||
@@ -137,7 +136,7 @@ void SpaceChildrenModel::insertChildren(std::vector<Quotient::GetSpaceHierarchyJ
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (children[i].childrenState.size() > 0) {
|
if (children[i].childrenState.size() > 0) {
|
||||||
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, quotientNone, quotientNone, 1);
|
auto job = m_space->connection()->callApi<Quotient::GetSpaceHierarchyJob>(children[i].roomId, std::nullopt, std::nullopt, 1);
|
||||||
m_currentJobs.append(job);
|
m_currentJobs.append(job);
|
||||||
connect(job, &Quotient::BaseJob::success, this, [this, parent, insertRow, job]() {
|
connect(job, &Quotient::BaseJob::success, this, [this, parent, insertRow, job]() {
|
||||||
insertChildren(job->rooms(), index(insertRow, 0, parent));
|
insertChildren(job->rooms(), index(insertRow, 0, parent));
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user