Compare commits
145 Commits
v24.07.80
...
work/nvrwh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
45c46ddcbb | ||
|
|
31d83ac0e3 | ||
|
|
909eec30d2 | ||
|
|
dbed3e99c2 | ||
|
|
4d0db0b5c2 | ||
|
|
6e49aaf17b | ||
|
|
1092d75f2e | ||
|
|
8059c3797d | ||
|
|
1302d62ad9 | ||
|
|
8eaae4034d | ||
|
|
354e3414a1 | ||
|
|
fc24beae6d | ||
|
|
923cc67b55 | ||
|
|
30e24069bc | ||
|
|
f7533a454c | ||
|
|
ab4e1a86dc | ||
|
|
d28c2ed113 | ||
|
|
3a467328f5 | ||
|
|
979d83cb01 | ||
|
|
d165cd955d | ||
|
|
6eb770343e | ||
|
|
54be52b855 | ||
|
|
d201333409 | ||
|
|
3db8b4cd17 | ||
|
|
0e246a00bc | ||
|
|
e638fa8929 | ||
|
|
cdc982ad91 | ||
|
|
bff69e21ad | ||
|
|
e117cc0cfb | ||
|
|
3af1a88e05 | ||
|
|
aa116a35f5 | ||
|
|
ac232d7f55 | ||
|
|
928911e33c | ||
|
|
e3c30f5bb3 | ||
|
|
4b51855528 | ||
|
|
79c27db0a9 | ||
|
|
78e42ab352 | ||
|
|
93909c45ee | ||
|
|
eda0bf4b23 | ||
|
|
4f4b10e0b6 | ||
|
|
2b374a8bec | ||
|
|
7821de4a8d | ||
|
|
86b88c851f | ||
|
|
1157882f1b | ||
|
|
594d1373c9 | ||
|
|
1e6948bbc7 | ||
|
|
41845b97d5 | ||
|
|
bb56f74622 | ||
|
|
a98f6ac331 | ||
|
|
15d6287995 | ||
|
|
0242ab72e8 | ||
|
|
4b7cbf37d5 | ||
|
|
0ccfe7d991 | ||
|
|
ab5585cd06 | ||
|
|
b25a0a7a4e | ||
|
|
f574c12adc | ||
|
|
05a2f03c18 | ||
|
|
cbc2e65856 | ||
|
|
1df80a7d2a | ||
|
|
050a014df7 | ||
|
|
4775ae09b0 | ||
|
|
8d4c3bb4fc | ||
|
|
439260ff03 | ||
|
|
5b04ad6805 | ||
|
|
6d77ed1e0e | ||
|
|
cc7f783b50 | ||
|
|
1d7ed1983b | ||
|
|
c0151353c5 | ||
|
|
72994248fa | ||
|
|
af0d3d5ee1 | ||
|
|
07dd1d2e91 | ||
|
|
185a88a16e | ||
|
|
e3059e636a | ||
|
|
aeb566746a | ||
|
|
8da567d9fa | ||
|
|
d99f69cc24 | ||
|
|
8240962400 | ||
|
|
bd1d4289c0 | ||
|
|
5d2139471a | ||
|
|
d49a64ac1e | ||
|
|
cd867ea581 | ||
|
|
32fd62c484 | ||
|
|
f2c561bd15 | ||
|
|
80734944d3 | ||
|
|
b3afa9f595 | ||
|
|
656558850c | ||
|
|
e41cca9be0 | ||
|
|
46916b34d4 | ||
|
|
4f9ca3e74d | ||
|
|
2bcdd0f52b | ||
|
|
56d790dda9 | ||
|
|
149013d2ff | ||
|
|
776807580a | ||
|
|
75e9eee3a9 | ||
|
|
183615fa7b | ||
|
|
7405fbaa16 | ||
|
|
22743b6d8b | ||
|
|
b5864f02cb | ||
|
|
373431fb1a | ||
|
|
29167fbdf2 | ||
|
|
699ac3a8e6 | ||
|
|
d7b572faaf | ||
|
|
a09b4e7f2c | ||
|
|
a794420d3e | ||
|
|
8b606266c5 | ||
|
|
11e4329d5e | ||
|
|
68dfc6ca81 | ||
|
|
e0c8945431 | ||
|
|
fe60ee00fb | ||
|
|
5990e00577 | ||
|
|
358a699290 | ||
|
|
da6e4378d9 | ||
|
|
5edb0629b3 | ||
|
|
5170854a2c | ||
|
|
37d6033df4 | ||
|
|
ea2f891533 | ||
|
|
42cec7d5ba | ||
|
|
2df2e39d43 | ||
|
|
b34525a4d8 | ||
|
|
0a7bd64b0d | ||
|
|
11fd4f88ec | ||
|
|
4d9452b862 | ||
|
|
060e30e612 | ||
|
|
ade7270add | ||
|
|
338428b0c0 | ||
|
|
54574e4450 | ||
|
|
ae564451b8 | ||
|
|
9a4504ce61 | ||
|
|
80785a2ff3 | ||
|
|
cb8ed02e82 | ||
|
|
a64c1b8eab | ||
|
|
d43aa169c3 | ||
|
|
8ae7141851 | ||
|
|
d3908db2c9 | ||
|
|
55de838a55 | ||
|
|
4cdfc38b87 | ||
|
|
51197d7c1a | ||
|
|
2a2a2e0c05 | ||
|
|
07fee30cc0 | ||
|
|
83c6ce0ace | ||
|
|
fb7303efa0 | ||
|
|
ef5585d312 | ||
|
|
11475259a1 | ||
|
|
9df4cd6f13 | ||
|
|
c9583964c8 |
@@ -2,5 +2,6 @@
|
||||
; SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
[BlueprintSettings]
|
||||
kde/applications/neochat.packageAppx=True
|
||||
kde/frameworks/extra-cmake-modules.version=master
|
||||
libs/qt.qtMajorVersion=6
|
||||
|
||||
@@ -13,3 +13,4 @@ include:
|
||||
- /gitlab-templates/craft-android-qt6-apks.yml
|
||||
- /gitlab-templates/craft-appimage-qt6.yml
|
||||
- /gitlab-templates/craft-windows-x86-64-qt6.yml
|
||||
- /gitlab-templates/craft-windows-appx-qt6.yml
|
||||
@@ -8,13 +8,13 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "07")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "6.0")
|
||||
set(KF_MIN_VERSION "6.5")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
@@ -38,6 +38,7 @@ include(KDEGitCommitHooks)
|
||||
include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
include(ECMAddAndroidApk)
|
||||
include(ECMQmlModule)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -59,7 +60,6 @@ set_package_properties(Qt6 PROPERTIES
|
||||
PURPOSE "Basic application components"
|
||||
)
|
||||
|
||||
qt_policy(SET QTP0001 NEW)
|
||||
if (QT_KNOWN_POLICY_QTP0004)
|
||||
qt_policy(SET QTP0004 NEW)
|
||||
endif ()
|
||||
|
||||
@@ -107,8 +107,13 @@ void ChatBarCacheTest::reply()
|
||||
void ChatBarCacheTest::edit()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
|
||||
chatBarCache->setText(QLatin1String("some text"));
|
||||
chatBarCache->setAttachmentPath(QLatin1String("some/path"));
|
||||
connect(chatBarCache.get(), &ChatBarCache::relationIdChanged, this, [](const QString &oldEventId, const QString &newEventId) {
|
||||
QCOMPARE(oldEventId, QString());
|
||||
QCOMPARE(newEventId, QString(QLatin1String("$153456789:example.org")));
|
||||
});
|
||||
chatBarCache->setEditId(QLatin1String("$153456789:example.org"));
|
||||
|
||||
QCOMPARE(chatBarCache->text(), QLatin1String("some text"));
|
||||
|
||||
@@ -29,15 +29,11 @@ private:
|
||||
Connection *connection = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
|
||||
EventHandler emptyHandler = EventHandler(nullptr, nullptr);
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
|
||||
void eventId();
|
||||
void nullEventId();
|
||||
void author();
|
||||
void nullAuthor();
|
||||
void authorDisplayName();
|
||||
void nullAuthorDisplayName();
|
||||
void singleLineSidplayName();
|
||||
@@ -45,7 +41,6 @@ private Q_SLOTS:
|
||||
void time();
|
||||
void nullTime();
|
||||
void timeString();
|
||||
void nullTimeString();
|
||||
void highlighted();
|
||||
void nullHighlighted();
|
||||
void hidden();
|
||||
@@ -67,10 +62,6 @@ private Q_SLOTS:
|
||||
void nullReplyId();
|
||||
void replyAuthor();
|
||||
void nullReplyAuthor();
|
||||
void replyBody();
|
||||
void nullReplyBody();
|
||||
void replyMediaInfo();
|
||||
void nullReplyMediaInfo();
|
||||
void thread();
|
||||
void nullThread();
|
||||
void location();
|
||||
@@ -85,182 +76,136 @@ void EventHandlerTest::initTestCase()
|
||||
|
||||
void EventHandlerTest::eventId()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandler.getId(), QStringLiteral("$153456789:example.org"));
|
||||
QCOMPARE(EventHandler::id(room->messageEvents().at(0).get()), QStringLiteral("$153456789:example.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullEventId()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getId called with m_event set to nullptr.");
|
||||
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.isLocalMember(), author.id() == room->localMember().id());
|
||||
QCOMPARE(eventHandlerAuthor.id(), author.id());
|
||||
QCOMPARE(eventHandlerAuthor.displayName(), author.displayName());
|
||||
QCOMPARE(eventHandlerAuthor.avatarUrl(), author.avatarUrl());
|
||||
QCOMPARE(eventHandlerAuthor.avatarMediaId(), author.avatarMediaId());
|
||||
QCOMPARE(eventHandlerAuthor.color(), author.color());
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullAuthor()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getAuthor(), RoomMember());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthor called with m_event set to nullptr. Returning empty user.");
|
||||
QCOMPARE(noEventHandler.getAuthor(), RoomMember());
|
||||
QTest::ignoreMessage(QtWarningMsg, "id called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::id(nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::authorDisplayName()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(1).get());
|
||||
QCOMPARE(eventHandler.getAuthorDisplayName(), QStringLiteral("before"));
|
||||
QCOMPARE(EventHandler::authorDisplayName(room, room->messageEvents().at(1).get()), QStringLiteral("before"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullAuthorDisplayName()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getAuthorDisplayName(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "authorDisplayName called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::authorDisplayName(nullptr, nullptr), QString());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getAuthorDisplayName(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "authorDisplayName called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::authorDisplayName(room, nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::singleLineSidplayName()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(11).get());
|
||||
QCOMPARE(eventHandler.singleLineAuthorDisplayname(), QStringLiteral("Look at me I put newlines in my display name"));
|
||||
QCOMPARE(EventHandler::singleLineAuthorDisplayname(room, room->messageEvents().at(11).get()),
|
||||
QStringLiteral("Look at me I put newlines in my display name"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullSingleLineDisplayName()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.singleLineAuthorDisplayname(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "singleLineAuthorDisplayname called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::singleLineAuthorDisplayname(nullptr, nullptr), QString());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.singleLineAuthorDisplayname(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "singleLineAuthorDisplayname called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::singleLineAuthorDisplayname(room, nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::time()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
|
||||
QCOMPARE(eventHandler.getTime(), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
|
||||
QCOMPARE(eventHandler.getTime(true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
|
||||
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
|
||||
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullTime()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getTime called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getTime(), QDateTime());
|
||||
QTest::ignoreMessage(QtWarningMsg, "time called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::time(nullptr), QDateTime());
|
||||
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QTest::ignoreMessage(QtWarningMsg, "a value must be provided for lastUpdated for a pending event.");
|
||||
QCOMPARE(eventHandler.getTime(true), QDateTime());
|
||||
QCOMPARE(EventHandler::time(room->messageEvents().at(0).get(), true), QDateTime());
|
||||
}
|
||||
|
||||
void EventHandlerTest::timeString()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
|
||||
KFormat format;
|
||||
|
||||
QCOMPARE(eventHandler.getTimeString(false),
|
||||
QCOMPARE(EventHandler::timeString(event, false),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(true),
|
||||
QCOMPARE(EventHandler::timeString(event, true),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(eventHandler.getTimeString(false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
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::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullTimeString()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getTimeString called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getTimeString(false), QString());
|
||||
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QTest::ignoreMessage(QtWarningMsg, "a value must be provided for lastUpdated for a pending event.");
|
||||
QCOMPARE(eventHandler.getTimeString(false, QLocale::ShortFormat, true), QString());
|
||||
QCOMPARE(EventHandler::timeString(event, QStringLiteral("hh:mm")),
|
||||
QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
|
||||
}
|
||||
|
||||
void EventHandlerTest::highlighted()
|
||||
{
|
||||
EventHandler eventHandlerHighlight(room, room->messageEvents().at(2).get());
|
||||
QCOMPARE(eventHandlerHighlight.isHighlighted(), true);
|
||||
|
||||
EventHandler eventHandlerNoHighlight(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoHighlight.isHighlighted(), false);
|
||||
QCOMPARE(EventHandler::isHighlighted(room, room->messageEvents().at(2).get()), true);
|
||||
QCOMPARE(EventHandler::isHighlighted(room, room->messageEvents().at(0).get()), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHighlighted()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isHighlighted(), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::isHighlighted(nullptr, nullptr), false);
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.isHighlighted(), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::isHighlighted(room, nullptr), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::hidden()
|
||||
{
|
||||
EventHandler eventHandlerHidden(room, room->messageEvents().at(3).get());
|
||||
QCOMPARE(eventHandlerHidden.isHidden(), true);
|
||||
|
||||
EventHandler eventHandlerNoHidden(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoHidden.isHidden(), false);
|
||||
QCOMPARE(EventHandler::isHidden(room, room->messageEvents().at(3).get()), true);
|
||||
QCOMPARE(EventHandler::isHidden(room, room->messageEvents().at(0).get()), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHidden()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isHidden(), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::isHidden(nullptr, nullptr), false);
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.isHidden(), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::isHidden(room, nullptr), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::body()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
|
||||
QCOMPARE(eventHandler.getRichBody(), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(eventHandler.getRichBody(true), QStringLiteral("<b>This is an example text message</b>"));
|
||||
QCOMPARE(eventHandler.getPlainBody(), QStringLiteral("This is an example\ntext message"));
|
||||
QCOMPARE(eventHandler.getPlainBody(true), QStringLiteral("This is an example text message"));
|
||||
QCOMPARE(EventHandler::richBody(room, event), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(EventHandler::richBody(room, event, true), QStringLiteral("<b>This is an example text message</b>"));
|
||||
QCOMPARE(EventHandler::plainBody(room, event), QStringLiteral("This is an example\ntext message"));
|
||||
QCOMPARE(EventHandler::plainBody(room, event, true), QStringLiteral("This is an example text message"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullBody()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "richBody called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::richBody(nullptr, nullptr), QString());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getRichBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getRichBody(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "richBody called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::richBody(room, nullptr), QString());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getPlainBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getPlainBody(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "plainBody called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::plainBody(nullptr, nullptr), QString());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "plainBody called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::plainBody(room, nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::genericBody_data()
|
||||
@@ -280,54 +225,45 @@ void EventHandlerTest::genericBody()
|
||||
QFETCH(int, eventNum);
|
||||
QFETCH(QString, output);
|
||||
|
||||
EventHandler eventHandler(room, room->messageEvents().at(eventNum).get());
|
||||
|
||||
QCOMPARE(eventHandler.getGenericBody(), output);
|
||||
QCOMPARE(EventHandler::genericBody(room->messageEvents().at(eventNum).get()), output);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullGenericBody()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getGenericBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getGenericBody(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "genericBody called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::genericBody(nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::markdownBody()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
|
||||
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
|
||||
QCOMPARE(EventHandler::markdownBody(room->messageEvents().at(0).get()), QStringLiteral("This is an example\ntext message"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::markdownBodyReply()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(5).get());
|
||||
|
||||
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("reply"));
|
||||
QCOMPARE(EventHandler::markdownBody(room->messageEvents().at(5).get()), QStringLiteral("reply"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::subtitle()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandler.subtitleText(), QStringLiteral("after: This is an example text message"));
|
||||
|
||||
EventHandler eventHandler2(room, room->messageEvents().at(2).get());
|
||||
QCOMPARE(eventHandler2.subtitleText(), QStringLiteral("after: This is a highlight @bob:kde.org and this is a link https://kde.org"));
|
||||
QCOMPARE(EventHandler::subtitleText(room, room->messageEvents().at(0).get()), QStringLiteral("after: This is an example text message"));
|
||||
QCOMPARE(EventHandler::subtitleText(room, room->messageEvents().at(2).get()),
|
||||
QStringLiteral("after: This is a highlight @bob:kde.org and this is a link https://kde.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullSubtitle()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.subtitleText(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::subtitleText(nullptr, nullptr), QString());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::subtitleText(room, nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::mediaInfo()
|
||||
{
|
||||
auto event = room->messageEvents().at(4).get();
|
||||
EventHandler eventHandler(room, event);
|
||||
|
||||
auto mediaInfo = eventHandler.getMediaInfo();
|
||||
auto mediaInfo = EventHandler::mediaInfo(room, event);
|
||||
auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
|
||||
|
||||
QCOMPARE(mediaInfo["source"_ls], room->makeMediaUrl(event->id(), QUrl("mxc://kde.org/1234567"_ls)));
|
||||
@@ -347,53 +283,42 @@ void EventHandlerTest::mediaInfo()
|
||||
|
||||
void EventHandlerTest::nullMediaInfo()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getMediaInfo(), QVariantMap());
|
||||
QTest::ignoreMessage(QtWarningMsg, "mediaInfo called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::mediaInfo(nullptr, nullptr), QVariantMap());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
|
||||
QTest::ignoreMessage(QtWarningMsg, "mediaInfo called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::mediaInfo(room, nullptr), QVariantMap());
|
||||
}
|
||||
|
||||
void EventHandlerTest::hasReply()
|
||||
{
|
||||
EventHandler eventHandlerReply(room, room->messageEvents().at(5).get());
|
||||
QCOMPARE(eventHandlerReply.hasReply(), true);
|
||||
|
||||
EventHandler eventHandlerNoReply(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoReply.hasReply(), false);
|
||||
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(5).get()), true);
|
||||
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(0).get()), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHasReply()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "hasReply called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.hasReply(), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "hasReply called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::hasReply(nullptr), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyId()
|
||||
{
|
||||
EventHandler eventHandlerReply(room, room->messageEvents().at(5).get());
|
||||
QCOMPARE(eventHandlerReply.getReplyId(), QStringLiteral("$153456789:example.org"));
|
||||
|
||||
EventHandler eventHandlerNoReply(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoReply.getReplyId(), QStringLiteral(""));
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(5).get()), QStringLiteral("$153456789:example.org"));
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(0).get()), QStringLiteral(""));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyId()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyId called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyId(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "replyId called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::replyId(nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyAuthor()
|
||||
{
|
||||
auto replyEvent = room->messageEvents().at(0).get();
|
||||
auto replyAuthor = room->member(replyEvent->senderId());
|
||||
EventHandler eventHandler(room, room->messageEvents().at(5).get());
|
||||
|
||||
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
|
||||
auto eventHandlerReplyAuthor = EventHandler::replyAuthor(room, room->messageEvents().at(5).get());
|
||||
|
||||
QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id());
|
||||
QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id());
|
||||
@@ -402,121 +327,58 @@ void EventHandlerTest::replyAuthor()
|
||||
QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId());
|
||||
QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color());
|
||||
|
||||
EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), RoomMember());
|
||||
QCOMPARE(EventHandler::replyAuthor(room, room->messageEvents().at(0).get()), RoomMember());
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyAuthor()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getReplyAuthor(), RoomMember());
|
||||
QTest::ignoreMessage(QtWarningMsg, "replyAuthor called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::replyAuthor(nullptr, nullptr), RoomMember());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_event set to nullptr. Returning empty user.");
|
||||
QCOMPARE(noEventHandler.getReplyAuthor(), RoomMember());
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyBody()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(5).get());
|
||||
|
||||
QCOMPARE(eventHandler.getReplyRichBody(), QStringLiteral("<b>This is an example<br>text message</b>"));
|
||||
QCOMPARE(eventHandler.getReplyRichBody(true), QStringLiteral("<b>This is an example text message</b>"));
|
||||
QCOMPARE(eventHandler.getReplyPlainBody(), QStringLiteral("This is an example\ntext message"));
|
||||
QCOMPARE(eventHandler.getReplyPlainBody(true), QStringLiteral("This is an example text message"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyBody()
|
||||
{
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyRichBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyRichBody(), QString());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyPlainBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyPlainBody(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyMediaInfo()
|
||||
{
|
||||
auto event = room->messageEvents().at(6).get();
|
||||
auto replyEvent = room->messageEvents().at(4).get();
|
||||
EventHandler eventHandler(room, event);
|
||||
|
||||
auto mediaInfo = eventHandler.getReplyMediaInfo();
|
||||
auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
|
||||
|
||||
QCOMPARE(mediaInfo["source"_ls], room->makeMediaUrl(replyEvent->id(), QUrl("mxc://kde.org/1234567"_ls)));
|
||||
QCOMPARE(mediaInfo["mimeType"_ls], QStringLiteral("video/mp4"));
|
||||
QCOMPARE(mediaInfo["mimeIcon"_ls], QStringLiteral("video-mp4"));
|
||||
QCOMPARE(mediaInfo["size"_ls], 62650636);
|
||||
QCOMPARE(mediaInfo["duration"_ls], 10);
|
||||
QCOMPARE(mediaInfo["width"_ls], 1920);
|
||||
QCOMPARE(mediaInfo["height"_ls], 1080);
|
||||
QCOMPARE(thumbnailInfo["source"_ls], room->makeMediaUrl(replyEvent->id(), QUrl("mxc://kde.org/2234567"_ls)));
|
||||
QCOMPARE(thumbnailInfo["mimeType"_ls], QStringLiteral("image/jpeg"));
|
||||
QCOMPARE(thumbnailInfo["mimeIcon"_ls], QStringLiteral("image-jpeg"));
|
||||
QCOMPARE(thumbnailInfo["size"_ls], 382249);
|
||||
QCOMPARE(thumbnailInfo["width"_ls], 800);
|
||||
QCOMPARE(thumbnailInfo["height"_ls], 450);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyMediaInfo()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyMediaInfo called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getReplyMediaInfo(), QVariantMap());
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyMediaInfo called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyMediaInfo(), QVariantMap());
|
||||
QTest::ignoreMessage(QtWarningMsg, "replyAuthor called with event set to nullptr. Returning empty user.");
|
||||
QCOMPARE(EventHandler::replyAuthor(room, nullptr), RoomMember());
|
||||
}
|
||||
|
||||
void EventHandlerTest::thread()
|
||||
{
|
||||
EventHandler eventHandlerNoThread(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoThread.isThreaded(), false);
|
||||
QCOMPARE(eventHandlerNoThread.threadRoot(), QString());
|
||||
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(0).get()), false);
|
||||
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(0).get()), QString());
|
||||
|
||||
EventHandler eventHandlerThreadRoot(room, room->messageEvents().at(9).get());
|
||||
QCOMPARE(eventHandlerThreadRoot.isThreaded(), true);
|
||||
QCOMPARE(eventHandlerThreadRoot.threadRoot(), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(eventHandlerThreadRoot.getReplyId(), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(9).get()), true);
|
||||
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(9).get()), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(9).get()), QStringLiteral("$threadroot:example.org"));
|
||||
|
||||
EventHandler eventHandlerThreadReply(room, room->messageEvents().at(10).get());
|
||||
QCOMPARE(eventHandlerThreadReply.isThreaded(), true);
|
||||
QCOMPARE(eventHandlerThreadReply.threadRoot(), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(eventHandlerThreadReply.getReplyId(), QStringLiteral("$threadmessage1:example.org"));
|
||||
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(10).get()), true);
|
||||
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(10).get()), QStringLiteral("$threadroot:example.org"));
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(10).get()), QStringLiteral("$threadmessage1:example.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullThread()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isThreaded(), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::isThreaded(nullptr), false);
|
||||
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.threadRoot(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::threadRoot(nullptr), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::location()
|
||||
{
|
||||
EventHandler eventHandler(room, room->messageEvents().at(7).get());
|
||||
|
||||
QCOMPARE(eventHandler.getLatitude(), QStringLiteral("51.7035").toFloat());
|
||||
QCOMPARE(eventHandler.getLongitude(), QStringLiteral("-1.14394").toFloat());
|
||||
QCOMPARE(eventHandler.getLocationAssetType(), QStringLiteral("m.pin"));
|
||||
QCOMPARE(EventHandler::latitude(room->messageEvents().at(7).get()), QStringLiteral("51.7035").toFloat());
|
||||
QCOMPARE(EventHandler::longitude(room->messageEvents().at(7).get()), QStringLiteral("-1.14394").toFloat());
|
||||
QCOMPARE(EventHandler::locationAssetType(room->messageEvents().at(7).get()), QStringLiteral("m.pin"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullLocation()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "getLatitude called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getLatitude(), -100.0);
|
||||
QTest::ignoreMessage(QtWarningMsg, "latitude called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::latitude(nullptr), -100.0);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getLongitude called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getLongitude(), -200.0);
|
||||
QTest::ignoreMessage(QtWarningMsg, "longitude called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::longitude(nullptr), -200.0);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "getLocationAssetType called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getLocationAssetType(), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "locationAssetType called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::locationAssetType(nullptr), QString());
|
||||
}
|
||||
|
||||
QTEST_MAIN(EventHandlerTest)
|
||||
|
||||
@@ -46,6 +46,7 @@ private Q_SLOTS:
|
||||
void sendCustomEmojiCode_data();
|
||||
void sendCustomEmojiCode();
|
||||
|
||||
void receiveSpacelessSelfClosingTag();
|
||||
void receiveStripReply();
|
||||
void receivePlainTextIn();
|
||||
|
||||
@@ -252,6 +253,19 @@ void TextHandlerTest::sendCustomEmojiCode()
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveSpacelessSelfClosingTag()
|
||||
{
|
||||
const QString testInputString = QStringLiteral("Test...<br/>...ing");
|
||||
const QString testRichOutputString = QStringLiteral("Test...<br/>...ing");
|
||||
const QString testPlainOutputString = QStringLiteral("Test...\n...ing");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testRichOutputString);
|
||||
QCOMPARE(testTextHandler.handleRecievePlainText(Qt::RichText), testPlainOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveStripReply()
|
||||
{
|
||||
const QString testInputString = QStringLiteral(
|
||||
@@ -274,6 +288,7 @@ void TextHandlerTest::receiveRichInPlainOut_data()
|
||||
QTest::newRow("ampersand") << QStringLiteral("a & b") << QStringLiteral("a & b");
|
||||
QTest::newRow("quote") << QStringLiteral(""a and b"") << QStringLiteral("\"a and b\"");
|
||||
QTest::newRow("new line") << QStringLiteral("new<br>line") << QStringLiteral("new\nline");
|
||||
QTest::newRow("unescape") << QStringLiteral("can't") << QStringLiteral("can't");
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichInPlainOut()
|
||||
@@ -448,6 +463,9 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
QString testOutputStringMxId = QStringLiteral(
|
||||
"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>");
|
||||
|
||||
QString testInputStringMxIdWithPrefix = QStringLiteral("a @user:kde.org b");
|
||||
QString testOutputStringMxIdWithPrefix = QStringLiteral("a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputStringLink1);
|
||||
|
||||
@@ -461,6 +479,9 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
|
||||
testTextHandler.setData(testInputStringMxId);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxId);
|
||||
|
||||
testTextHandler.setData(testInputStringMxIdWithPrefix);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText), testOutputStringMxIdWithPrefix);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveRichEdited_data()
|
||||
@@ -535,7 +556,7 @@ void TextHandlerTest::componentOutput_data()
|
||||
"someField }\nCustomQml {\n someTextProperty: someField.text\n}\n</code></pre>Sure you can, it's still local to the same file where you "
|
||||
"defined the id")
|
||||
<< QList<MessageComponent>{
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like"), {}},
|
||||
MessageComponent{MessageComponentType::Text, QStringLiteral("Ah, you mean something like<br/>"), {}},
|
||||
MessageComponent{
|
||||
MessageComponentType::Code,
|
||||
QStringLiteral(
|
||||
|
||||
@@ -17,7 +17,6 @@ class WindowControllerTest : public QObject
|
||||
|
||||
private Q_SLOTS:
|
||||
void nullWindow();
|
||||
void geometry();
|
||||
void showAndRaise();
|
||||
void toggle();
|
||||
|
||||
@@ -30,32 +29,10 @@ void WindowControllerTest::nullWindow()
|
||||
auto &instance = WindowController::instance();
|
||||
QCOMPARE(instance.window(), nullptr);
|
||||
|
||||
instance.restoreGeometry();
|
||||
instance.saveGeometry();
|
||||
instance.showAndRaiseWindow({});
|
||||
instance.toggleWindow();
|
||||
}
|
||||
|
||||
void WindowControllerTest::geometry()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
|
||||
QWindow window;
|
||||
window.setGeometry(0, 0, 200, 200);
|
||||
instance.setWindow(&window);
|
||||
QCOMPARE(instance.window(), &window);
|
||||
|
||||
instance.saveGeometry();
|
||||
const auto stateConfig = KSharedConfig::openStateConfig();
|
||||
KConfigGroup windowGroup = stateConfig->group(QStringLiteral("Window"));
|
||||
QCOMPARE(KWindowConfig::hasSavedWindowSize(windowGroup), true);
|
||||
|
||||
window.setGeometry(0, 0, 400, 400);
|
||||
QCOMPARE(window.geometry(), QRect(0, 0, 400, 400));
|
||||
instance.restoreGeometry();
|
||||
QCOMPARE(window.geometry(), QRect(0, 0, 200, 200));
|
||||
}
|
||||
|
||||
void WindowControllerTest::showAndRaise()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
<name xml:lang="fi">NeoChat</name>
|
||||
<name xml:lang="fr">NeoChat</name>
|
||||
<name xml:lang="gl">NeoChat</name>
|
||||
<name xml:lang="he">NeoChat</name>
|
||||
<name xml:lang="hu">NeoChat</name>
|
||||
<name xml:lang="ia">Neochat</name>
|
||||
<name xml:lang="id">NeoChat</name>
|
||||
@@ -61,6 +62,7 @@
|
||||
<summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
|
||||
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
|
||||
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
||||
<summary xml:lang="he">התכתבות עם החברים שלך ב־matrix</summary>
|
||||
<summary xml:lang="hu">Csevegjen barátaival a matrixon</summary>
|
||||
<summary xml:lang="ia">Starta Conversation con tu amicos sur matrix</summary>
|
||||
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
|
||||
@@ -89,6 +91,8 @@
|
||||
<p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p>
|
||||
<p xml:lang="fi">NeoChat on keskustelusovellus, jolla Matrix-verkosta saa täyden hyödyn. Se tarjoaa salatun kanavan lähettää perheelle, työkavereille ja ystäville tekstiviestejä sekä video- ja äänitiedostoja.</p>
|
||||
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
|
||||
<p xml:lang="gl">NeoChat é unha aplicación de conversa que lle permite usar todas as funcionalidades da rede Matrix. Fornece unha forma segura de enviar mensaxes de texto e ficheiros de vídeo e son a familiares, amizades ou no traballo.</p>
|
||||
<p xml:lang="he">NeoChat הוא יישום התכתבות שמאפשר לך לנצל את רשת Matrix במלואה. הוא מספק דרך מאובטחת לשליחת הודעות כתובות, סרטונים וקובצי שמע למשפחה, לעמיתים לעבודה ולחברים.</p>
|
||||
<p xml:lang="hu">A NeoChat egy olyan csevegőalkalmazás, amellyel teljes mértékben kihasználhatja a Matrix hálózatot. Biztonságos módot biztosít szöveges üzenetek, videók és hangfájlok küldéséhez családtagjainak, kollégáinak és barátainak.</p>
|
||||
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
|
||||
<p xml:lang="it">NeoChat è un'applicazione di chat che ti consente di sfruttare appieno la rete Matrix. Ti fornisce un modo sicuro per inviare messaggi di testo, video e file audio a familiari, colleghi e amici.</p>
|
||||
@@ -114,6 +118,7 @@
|
||||
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
|
||||
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
|
||||
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
|
||||
<p xml:lang="he">NeoChat מתיימר להיות יישום עתיר יכולות לפי מפרט Matrix. כיוון שזה ייעודו, כל מה שבמפרט היציב עם חריגות משמעותיות כגון VoIP, שרשורים ועוד מגוון היבטים של הצפנה מקצה לקצה נתמכים גם הם. יש מספר השמטות קטן עקב העובדה שהמפרט של Matrix ממשיך להתפתח אך המטרה היא להמשיך לספק תמיכה בסופו של דבר לכל המפרט.</p>
|
||||
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
|
||||
<p xml:lang="ia">NeoChat aspira a esser un application plenmente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
|
||||
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
|
||||
@@ -142,6 +147,7 @@
|
||||
<p xml:lang="fi">Matrix-määritelmän kehittyessä NeoChat tukee myös monia epävakaita ominaisuuksia. Tällä hetkellä näitä ovat:</p>
|
||||
<p xml:lang="fr">En raison de la nature du développement des spécifications du protocole Matrix, NeoChat prend également en charge de nombreuses fonctionnalités instables. Actuellement, ce sont :</p>
|
||||
<p xml:lang="gl">Debido á natureza do desenvolvemento da especificación de Matrix, NeoChat tamén inclúe varias funcionalidades non estábeis:</p>
|
||||
<p xml:lang="he">מטבע הדברים, הפיתוח של NeoChat תומך במגוון יכולות מפוקפקות כתלות בהתפתחות המפרט הטכני של Matrix. היכולות האלה הן:</p>
|
||||
<p xml:lang="hu">A Matrix specifikáció fejlesztésének jellegéből adódóan a NeoChat számos instabil funkciót is támogat. Jelenleg a következőket:</p>
|
||||
<p xml:lang="ia">Debite al natura del disveloppamento de specification de Matrix NeoChat tamben supporta numerose characteristicas instabile. Currentemente istes es:</p>
|
||||
<p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p>
|
||||
@@ -172,6 +178,7 @@
|
||||
<li xml:lang="fi">Kyselyt – MSC3381</li>
|
||||
<li xml:lang="fr">Sondages - MSC3381</li>
|
||||
<li xml:lang="gl">Enquisas — MSC3381</li>
|
||||
<li xml:lang="he">סקרים - MSC3381</li>
|
||||
<li xml:lang="hu">Szavazások - MSC3381</li>
|
||||
<li xml:lang="ia">Inquestas - MSC3381</li>
|
||||
<li xml:lang="it">Sondaggi - MSC3381</li>
|
||||
@@ -200,6 +207,7 @@
|
||||
<li xml:lang="fi">Tarrapakkaukset – MSC2545</li>
|
||||
<li xml:lang="fr">Paquets d'auto-collants - MSC2545</li>
|
||||
<li xml:lang="gl">Paquetes de adhesivos — MSC2545</li>
|
||||
<li xml:lang="he">חבילות מדבקות - MSC2545</li>
|
||||
<li xml:lang="hu">Matricacsomagok - MSC2545</li>
|
||||
<li xml:lang="ia">Etiquetta gummate (sticker) -MSC2545</li>
|
||||
<li xml:lang="it">Pacchetti di adesivi - MSC2545</li>
|
||||
@@ -229,6 +237,7 @@
|
||||
<li xml:lang="fi">Sijaintitapahtumat – MSC3488</li>
|
||||
<li xml:lang="fr">Événements de lieu - MSC3488</li>
|
||||
<li xml:lang="gl">Localización de eventos — MSC3488</li>
|
||||
<li xml:lang="he">אירועי מקום - MSC3488</li>
|
||||
<li xml:lang="hu">Események helyadatai - MSC3488</li>
|
||||
<li xml:lang="ia">Eventos de Location - MSC3488</li>
|
||||
<li xml:lang="it">Località eventi - MSC3488</li>
|
||||
@@ -292,6 +301,7 @@
|
||||
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
|
||||
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
|
||||
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
|
||||
<caption xml:lang="he">תצוגה ראשית עם רשימת חדרים, צ׳אט ופרטי חדר</caption>
|
||||
<caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption>
|
||||
<caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption>
|
||||
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
|
||||
@@ -323,6 +333,8 @@
|
||||
<caption xml:lang="eu">Ezagutu komunitate berriak Matrixeko Tokiak erabiliz</caption>
|
||||
<caption xml:lang="fi">Löydä uusia yhteisöjä Matrix Spacesillä</caption>
|
||||
<caption xml:lang="fr">Découvrez de nouvelles communautés avec les espaces sous Matrix</caption>
|
||||
<caption xml:lang="gl">Descubra novas comunidades dos espazos de Matrix.</caption>
|
||||
<caption xml:lang="he">אפשר להיחשף לקהילות חדשות דרך Matrix Spaces</caption>
|
||||
<caption xml:lang="hu">Fedezzen fel új közösségeket a Matrix Terek segítségével</caption>
|
||||
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</caption>
|
||||
<caption xml:lang="it">Scopri nuove comunità con Matrix Spaces</caption>
|
||||
@@ -358,6 +370,7 @@
|
||||
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
|
||||
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
|
||||
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
|
||||
<caption xml:lang="he">תצוגה ראשית עם רשימת חדרים, צ׳אט ופרטי חדר</caption>
|
||||
<caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption>
|
||||
<caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption>
|
||||
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
|
||||
@@ -391,6 +404,7 @@
|
||||
<caption xml:lang="fi">Kirjautumisnäkymä</caption>
|
||||
<caption xml:lang="fr">Écran de connexion</caption>
|
||||
<caption xml:lang="gl">Pantalla de identificación.</caption>
|
||||
<caption xml:lang="he">מסך כניסה</caption>
|
||||
<caption xml:lang="hu">Bejelentkező képernyő</caption>
|
||||
<caption xml:lang="ia">Schermo de accesso</caption>
|
||||
<caption xml:lang="it">Schermata di accesso</caption>
|
||||
@@ -415,6 +429,8 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="24.08.1" date="2024-09-12"/>
|
||||
<release version="24.08.0" date="2024-08-22"/>
|
||||
<release version="24.05.2" date="2024-07-04"/>
|
||||
<release version="24.05.1" date="2024-06-13"/>
|
||||
<release version="24.05.0" date="2024-05-23"/>
|
||||
|
||||
@@ -18,6 +18,7 @@ Name[eu]=NeoChat
|
||||
Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[gl]=NeoChat
|
||||
Name[he]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
@@ -59,6 +60,7 @@ GenericName[eu]=Matrix bezeroa
|
||||
GenericName[fi]=Matrix-asiakas
|
||||
GenericName[fr]=Client « Matrix »
|
||||
GenericName[gl]=Cliente de Matrix
|
||||
GenericName[he]=לקוח Matrix
|
||||
GenericName[hu]=Matrix kliens
|
||||
GenericName[ia]=Cliente de Matrice
|
||||
GenericName[id]=Klien Matrix
|
||||
@@ -99,6 +101,7 @@ Comment[eu]=Matrix protokolorako bezeroa
|
||||
Comment[fi]=Asiakas Matrix-yhteyskäytännölle
|
||||
Comment[fr]=Client pour le protocole « Matrix »
|
||||
Comment[gl]=Cliente para o protocolo Matrix.
|
||||
Comment[he]=לקוח לפרוטוקול Matrix
|
||||
Comment[hu]=Kliens a Matrix protokollhoz
|
||||
Comment[ia]=Cliente per le protocollo de Matrix
|
||||
Comment[id]=Klien untuk protokol Matrix
|
||||
|
||||
1278
po/ar/neochat.po
1278
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
1123
po/ast/neochat.po
1123
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
1309
po/az/neochat.po
1309
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
1234
po/ca/neochat.po
1234
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1257
po/cs/neochat.po
1257
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
1246
po/da/neochat.po
1246
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1317
po/de/neochat.po
1317
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
1320
po/el/neochat.po
1320
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
1265
po/en_GB/neochat.po
1265
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
1277
po/eo/neochat.po
1277
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
1238
po/es/neochat.po
1238
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
1237
po/eu/neochat.po
1237
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1316
po/fi/neochat.po
1316
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1247
po/fr/neochat.po
1247
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
5724
po/gl/neochat.po
Normal file
5724
po/gl/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1645
po/hu/neochat.po
1645
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
1235
po/ia/neochat.po
1235
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
1320
po/id/neochat.po
1320
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
1276
po/ie/neochat.po
1276
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
1339
po/it/neochat.po
1339
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
1123
po/ja/neochat.po
1123
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
1237
po/ka/neochat.po
1237
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1312
po/ko/neochat.po
1312
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
1123
po/lt/neochat.po
1123
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
1304
po/lv/neochat.po
1304
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
1237
po/nl/neochat.po
1237
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
1220
po/nn/neochat.po
1220
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
1308
po/pa/neochat.po
1308
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1234
po/pl/neochat.po
1234
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
1318
po/pt/neochat.po
1318
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
1373
po/pt_BR/neochat.po
1373
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
1307
po/ru/neochat.po
1307
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
1314
po/sk/neochat.po
1314
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1228
po/sl/neochat.po
1228
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
1233
po/sv/neochat.po
1233
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
1250
po/ta/neochat.po
1250
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
1259
po/tok/neochat.po
1259
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
1237
po/tr/neochat.po
1237
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
1243
po/uk/neochat.po
1243
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1141
po/zh_CN/neochat.po
1141
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
1222
po/zh_TW/neochat.po
1222
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/neochatchangepasswordjob.cpp
|
||||
jobs/neochatchangepasswordjob.h
|
||||
jobs/neochatgetcommonroomsjob.cpp
|
||||
jobs/neochatgetcommonroomsjob.h
|
||||
mediasizehelper.cpp
|
||||
mediasizehelper.h
|
||||
eventhandler.cpp
|
||||
@@ -188,6 +190,10 @@ add_library(neochat STATIC
|
||||
threepidbindhelper.h
|
||||
models/readmarkermodel.cpp
|
||||
models/readmarkermodel.h
|
||||
neochatroommember.cpp
|
||||
neochatroommember.h
|
||||
models/threadmodel.cpp
|
||||
models/threadmodel.h
|
||||
)
|
||||
|
||||
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
|
||||
@@ -236,11 +242,9 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/EditMenu.qml
|
||||
qml/MessageDelegateContextMenu.qml
|
||||
qml/FileDelegateContextMenu.qml
|
||||
qml/FileDelegateContextMenuMobile.qml
|
||||
qml/MessageSourceSheet.qml
|
||||
qml/ReportSheet.qml
|
||||
qml/ConfirmEncryptionDialog.qml
|
||||
qml/RemoveSheet.qml
|
||||
qml/BanSheet.qml
|
||||
qml/RoomSearchPage.qml
|
||||
qml/LocationChooser.qml
|
||||
qml/TimelineView.qml
|
||||
@@ -284,6 +288,8 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/ConsentDialog.qml
|
||||
qml/AskDirectChatConfirmation.qml
|
||||
qml/HoverLinkIndicator.qml
|
||||
qml/AvatarNotification.qml
|
||||
qml/ReasonDialog.qml
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
QtQuick
|
||||
@@ -301,7 +307,7 @@ add_subdirectory(devtools)
|
||||
add_subdirectory(login)
|
||||
add_subdirectory(chatbar)
|
||||
|
||||
if(UNIX)
|
||||
if(NOT ANDROID AND NOT WIN32)
|
||||
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
|
||||
else()
|
||||
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
|
||||
@@ -421,7 +427,7 @@ if (TARGET KF6::Crash)
|
||||
target_link_libraries(neochat PUBLIC KF6::Crash)
|
||||
endif()
|
||||
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
kconfig_target_kcfg_file(neochat FILE neochatconfig.kcfg CLASS_NAME NeoChatConfig MUTATORS GENERATE_PROPERTIES DEFAULT_VALUE_GETTERS PARENT_IN_CONSTRUCTOR SINGLETON GENERATE_MOC QML_REGISTRATION)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
target_compile_definitions(neochat PUBLIC NEOCHAT_FLATPAK)
|
||||
|
||||
@@ -56,7 +56,7 @@ void ActionsHandler::handleMessageEvent(ChatBarCache *chatBarCache)
|
||||
|
||||
QString handledText = chatBarCache->text();
|
||||
handledText = handleMentions(handledText, chatBarCache->mentions());
|
||||
handleMessage(m_room->mainCache()->text(), handledText, chatBarCache);
|
||||
handleMessage(chatBarCache->text(), handledText, chatBarCache);
|
||||
}
|
||||
|
||||
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *mentions)
|
||||
|
||||
@@ -219,7 +219,7 @@ QQC2.Control {
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (!repeatTimer.running && Config.typingNotifications) {
|
||||
if (!repeatTimer.running && NeoChatConfig.typingNotifications) {
|
||||
var textExists = text.length > 0;
|
||||
root.currentRoom.sendTypingNotification(textExists);
|
||||
textExists ? repeatTimer.start() : repeatTimer.stop();
|
||||
@@ -353,8 +353,8 @@ QQC2.Control {
|
||||
startBreakpoint: Kirigami.Units.gridUnit * 46
|
||||
endBreakpoint: Kirigami.Units.gridUnit * 66
|
||||
startPercentWidth: 100
|
||||
endPercentWidth: Config.compactLayout ? 100 : 85
|
||||
maxWidth: Config.compactLayout ? -1 : Kirigami.Units.gridUnit * 60
|
||||
endPercentWidth: NeoChatConfig.compactLayout ? 100 : 85
|
||||
maxWidth: NeoChatConfig.compactLayout ? -1 : Kirigami.Units.gridUnit * 60
|
||||
|
||||
parentWidth: root.width
|
||||
}
|
||||
@@ -395,7 +395,7 @@ QQC2.Control {
|
||||
repeatTimer.stop();
|
||||
root.currentRoom.markAllMessagesAsRead();
|
||||
textField.clear();
|
||||
_private.chatBarCache.replyId = "";
|
||||
_private.chatBarCache.clearRelations();
|
||||
messageSent();
|
||||
}
|
||||
|
||||
|
||||
@@ -119,8 +119,7 @@ QString ChatBarCache::relationMessage() const
|
||||
}
|
||||
|
||||
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
|
||||
EventHandler eventhandler(room, &**event);
|
||||
return eventhandler.getMarkdownBody();
|
||||
return EventHandler::markdownBody(&**event);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -140,8 +139,8 @@ void ChatBarCache::setThreadId(const QString &threadId)
|
||||
if (m_threadId == threadId) {
|
||||
return;
|
||||
}
|
||||
m_threadId = threadId;
|
||||
Q_EMIT threadIdChanged();
|
||||
const auto oldThreadId = std::exchange(m_threadId, threadId);
|
||||
Q_EMIT threadIdChanged(oldThreadId, m_threadId);
|
||||
}
|
||||
|
||||
QString ChatBarCache::attachmentPath() const
|
||||
@@ -161,6 +160,16 @@ void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
}
|
||||
|
||||
void ChatBarCache::clearRelations()
|
||||
{
|
||||
const auto oldEventId = std::exchange(m_relationId, QString());
|
||||
const auto oldThreadId = std::exchange(m_threadId, QString());
|
||||
m_attachmentPath = QString();
|
||||
Q_EMIT relationIdChanged(oldEventId, m_relationId);
|
||||
Q_EMIT threadIdChanged(oldThreadId, m_threadId);
|
||||
Q_EMIT attachmentPathChanged();
|
||||
}
|
||||
|
||||
QList<Mention> *ChatBarCache::mentions()
|
||||
{
|
||||
return &m_mentions;
|
||||
|
||||
@@ -33,7 +33,7 @@ struct Mention {
|
||||
* A class to cache data from a chat bar.
|
||||
*
|
||||
* A chat bar can be anything that allows users to compose or edit message, it doesn't
|
||||
* necessarily have to use the ChatBar component, e.g. MessageEditComponent.
|
||||
* necessarily have to use the ChatBar component, e.g. ChatBarComponent.
|
||||
*
|
||||
* This object is intended to allow the current contents of a chat bar to be cached
|
||||
* between different rooms, i.e. there is an expectation that each NeoChatRoom could
|
||||
@@ -43,7 +43,7 @@ struct Mention {
|
||||
* as it's parent. This is necessary for certain functions which need to get
|
||||
* relevant room information.
|
||||
*
|
||||
* @sa ChatBar, MessageEditComponent, NeoChatRoom
|
||||
* @sa ChatBar, ChatBarComponent, NeoChatRoom
|
||||
*/
|
||||
class ChatBarCache : public QObject
|
||||
{
|
||||
@@ -165,6 +165,13 @@ public:
|
||||
QString attachmentPath() const;
|
||||
void setAttachmentPath(const QString &attachmentPath);
|
||||
|
||||
/**
|
||||
* @brief Clear all relations in the cache.
|
||||
*
|
||||
* This includes relation ID, thread root ID and attachment path.
|
||||
*/
|
||||
Q_INVOKABLE void clearRelations();
|
||||
|
||||
/**
|
||||
* @brief Retrieve the mentions for the current chat bar text.
|
||||
*/
|
||||
@@ -188,7 +195,7 @@ public:
|
||||
Q_SIGNALS:
|
||||
void textChanged();
|
||||
void relationIdChanged(const QString &oldEventId, const QString &newEventId);
|
||||
void threadIdChanged();
|
||||
void threadIdChanged(const QString &oldThreadId, const QString &newThreadId);
|
||||
void attachmentPathChanged();
|
||||
|
||||
private:
|
||||
|
||||
@@ -26,23 +26,10 @@ void ColorSchemer::apply(int idx)
|
||||
c->activateScheme(c->model()->index(idx, 0));
|
||||
}
|
||||
|
||||
void ColorSchemer::apply(const QString &name)
|
||||
int ColorSchemer::indexForCurrentScheme()
|
||||
{
|
||||
c->activateScheme(c->indexForScheme(name));
|
||||
}
|
||||
|
||||
int ColorSchemer::indexForScheme(const QString &name) const
|
||||
{
|
||||
auto index = c->indexForScheme(name).row();
|
||||
if (index == -1) {
|
||||
index = 0;
|
||||
}
|
||||
return index;
|
||||
}
|
||||
|
||||
QString ColorSchemer::nameForIndex(int index) const
|
||||
{
|
||||
return c->model()->data(c->model()->index(index, 0), Qt::DisplayRole).toString();
|
||||
return -1;
|
||||
// return c->indexForSchemeId(c->activeSchemeId()).row();
|
||||
}
|
||||
|
||||
#include "moc_colorschemer.cpp"
|
||||
|
||||
@@ -44,21 +44,11 @@ public:
|
||||
Q_INVOKABLE void apply(int idx);
|
||||
|
||||
/**
|
||||
* @brief Activates the KColorScheme with the given name.
|
||||
* @brief Get the row for the current color scheme.
|
||||
*
|
||||
* @sa KColorScheme
|
||||
*/
|
||||
Q_INVOKABLE void apply(const QString &name);
|
||||
|
||||
/**
|
||||
* @brief Returns the index for the scheme with the given name.
|
||||
*/
|
||||
Q_INVOKABLE int indexForScheme(const QString &name) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the name for the scheme with the given index.
|
||||
*/
|
||||
Q_INVOKABLE QString nameForIndex(int index) const;
|
||||
Q_INVOKABLE int indexForCurrentScheme();
|
||||
|
||||
private:
|
||||
KColorSchemeManager *c;
|
||||
|
||||
@@ -13,13 +13,11 @@
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Quotient/settings.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "proxycontroller.h"
|
||||
@@ -64,7 +62,11 @@ Controller::Controller(QObject *parent)
|
||||
});
|
||||
} else {
|
||||
auto c = new NeoChatConnection(this);
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("device_1234"), QStringLiteral("token_1234"));
|
||||
#else
|
||||
c->assumeIdentity(QStringLiteral("@user:localhost:1234"), QStringLiteral("token_1234"));
|
||||
#endif
|
||||
connect(c, &Connection::connected, this, [c, this]() {
|
||||
m_accountRegistry.add(c);
|
||||
c->syncLoop();
|
||||
@@ -202,15 +204,33 @@ void Controller::invokeLogin()
|
||||
m_connectionsLoading[accountId] = connection;
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
||||
connection->loadState();
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
if (connection->allRooms().size() == 0 || connection->allRooms()[0]->currentState().get<RoomCreateEvent>()) {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
} else {
|
||||
connect(
|
||||
connection->allRooms()[0],
|
||||
&Room::baseStateLoaded,
|
||||
this,
|
||||
[this, connection, accountId]() {
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
});
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error), {});
|
||||
});
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
|
||||
#else
|
||||
connection->assumeIdentity(account.userId(), accessToken);
|
||||
#endif
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -251,23 +271,19 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QStrin
|
||||
return job;
|
||||
}
|
||||
|
||||
bool Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken)
|
||||
void Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken)
|
||||
{
|
||||
qDebug() << "Save the access token to the keychain for " << userId;
|
||||
QKeychain::WritePasswordJob job(qAppName());
|
||||
job.setAutoDelete(false);
|
||||
job.setKey(userId);
|
||||
job.setBinaryData(accessToken);
|
||||
QEventLoop loop;
|
||||
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);
|
||||
job.start();
|
||||
loop.exec();
|
||||
|
||||
if (job.error()) {
|
||||
qWarning() << "Could not save access token to the keychain: " << qPrintable(job.errorString());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
auto job = new QKeychain::WritePasswordJob(qAppName());
|
||||
job->setAutoDelete(true);
|
||||
job->setKey(userId);
|
||||
job->setBinaryData(accessToken);
|
||||
connect(job, &QKeychain::WritePasswordJob::finished, this, [job]() {
|
||||
if (job->error()) {
|
||||
qWarning() << "Could not save access token to the keychain: " << qPrintable(job->errorString());
|
||||
}
|
||||
});
|
||||
job->start();
|
||||
}
|
||||
|
||||
bool Controller::supportSystemTray() const
|
||||
@@ -409,11 +425,28 @@ void Controller::removeConnection(const QString &userId)
|
||||
|
||||
bool Controller::csSupported() const
|
||||
{
|
||||
#if Quotient_VERSION_MINOR > 9
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::revertToDefaultConfig()
|
||||
{
|
||||
const auto config = NeoChatConfig::self();
|
||||
config->setDefaults();
|
||||
config->save();
|
||||
}
|
||||
|
||||
bool Controller::isImageShown(const QString &eventId)
|
||||
{
|
||||
return m_shownImages.contains(eventId);
|
||||
}
|
||||
|
||||
void Controller::markImageShown(const QString &eventId)
|
||||
{
|
||||
m_shownImages.append(eventId);
|
||||
}
|
||||
|
||||
#include "moc_controller.cpp"
|
||||
|
||||
@@ -86,7 +86,7 @@ public:
|
||||
/**
|
||||
* @brief Save an access token to the keychain for the given account.
|
||||
*/
|
||||
bool saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
|
||||
void saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
|
||||
|
||||
[[nodiscard]] bool supportSystemTray() const;
|
||||
|
||||
@@ -108,6 +108,17 @@ public:
|
||||
|
||||
bool csSupported() const;
|
||||
|
||||
/**
|
||||
* @brief Revert all configuration values to their default.
|
||||
*
|
||||
* The parameters along with their defaults are specified in the config file
|
||||
* neochatconfig.kcfg.
|
||||
*/
|
||||
Q_INVOKABLE void revertToDefaultConfig();
|
||||
|
||||
Q_INVOKABLE bool isImageShown(const QString &eventId);
|
||||
Q_INVOKABLE void markImageShown(const QString &eventId);
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
@@ -116,13 +127,11 @@ private:
|
||||
|
||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const QString &account);
|
||||
|
||||
void loadSettings();
|
||||
void saveSettings() const;
|
||||
|
||||
Quotient::AccountRegistry m_accountRegistry;
|
||||
QStringList m_accountsLoading;
|
||||
QMap<QString, QPointer<NeoChatConnection>> m_connectionsLoading;
|
||||
QString m_endpoint;
|
||||
QStringList m_shownImages;
|
||||
|
||||
private Q_SLOTS:
|
||||
void invokeLogin();
|
||||
|
||||
@@ -17,25 +17,25 @@ FormCard.FormCardPage {
|
||||
|
||||
FormCard.FormCheckDelegate {
|
||||
text: i18nc("@option:check", "Show hidden events in the timeline")
|
||||
checked: Config.showAllEvents
|
||||
checked: NeoChatConfig.showAllEvents
|
||||
|
||||
onToggled: Config.showAllEvents = checked
|
||||
onToggled: NeoChatConfig.showAllEvents = checked
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
id: roomAccountDataVisibleCheck
|
||||
text: i18nc("@option:check Enable the matrix 'threads' feature", "Always allow device verification")
|
||||
description: i18n("Allow the user to start a verification session with devices that were already verified")
|
||||
checked: Config.alwaysVerifyDevice
|
||||
checked: NeoChatConfig.alwaysVerifyDevice
|
||||
|
||||
onToggled: Config.alwaysVerifyDevice = checked
|
||||
onToggled: NeoChatConfig.alwaysVerifyDevice = checked
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
text: i18nc("@option:check", "Show focus in window header")
|
||||
checked: Config.windowTitleFocus
|
||||
checked: NeoChatConfig.windowTitleFocus
|
||||
|
||||
onToggled: {
|
||||
Config.windowTitleFocus = checked;
|
||||
Config.save();
|
||||
NeoChatConfig.windowTitleFocus = checked;
|
||||
NeoChatConfig.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,23 +18,23 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCheckDelegate {
|
||||
id: roomAccountDataVisibleCheck
|
||||
text: i18nc("@option:check Enable the matrix 'threads' feature", "Threads")
|
||||
checked: Config.threads
|
||||
checked: NeoChatConfig.threads
|
||||
|
||||
onToggled: Config.threads = checked
|
||||
onToggled: NeoChatConfig.threads = checked
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
text: i18nc("@option:check Enable the matrix 'secret backup' feature", "Secret Backup")
|
||||
checked: Config.secretBackup
|
||||
checked: NeoChatConfig.secretBackup
|
||||
|
||||
onToggled: Config.secretBackup = checked
|
||||
onToggled: NeoChatConfig.secretBackup = checked
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs")
|
||||
checked: Config.phone3PId
|
||||
checked: NeoChatConfig.phone3PId
|
||||
|
||||
onToggled: {
|
||||
Config.phone3PId = checked
|
||||
Config.save();
|
||||
NeoChatConfig.phone3PId = checked
|
||||
NeoChatConfig.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ public:
|
||||
ReadMarker, /**< The local user read marker. */
|
||||
Loading, /**< A delegate to tell the user more messages are being loaded. */
|
||||
TimelineEnd, /**< A delegate to inform that all messages are loaded. */
|
||||
Predecessor, /**< A delegate to show a room predecessor. */
|
||||
Successor, /**< A delegate to show a room successor. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
};
|
||||
Q_ENUM(Type);
|
||||
|
||||
@@ -50,7 +50,7 @@ public:
|
||||
Reply, /**< A component to show a replied-to message. */
|
||||
LinkPreview, /**< A preview of a URL in the message. */
|
||||
LinkPreviewLoad, /**< A loading dialog for a link preview. */
|
||||
Edit, /**< A text edit for editing a message. */
|
||||
ChatBar, /**< A text edit for editing a message. */
|
||||
Verification, /**< A user verification session start message. */
|
||||
Loading, /**< The component is loading. */
|
||||
Other, /**< Anything that cannot be classified as another type. */
|
||||
|
||||
@@ -5,16 +5,17 @@
|
||||
|
||||
#include <QMovie>
|
||||
|
||||
#include <KFormat>
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <Quotient/eventitem.h>
|
||||
#include <Quotient/events/encryptionevent.h>
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/events/reactionevent.h>
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roomavatarevent.h>
|
||||
#include <Quotient/events/roomcanonicalaliasevent.h>
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/events/roommemberevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
#include <Quotient/events/simplestateevents.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
@@ -25,9 +26,6 @@
|
||||
#include "events/locationbeaconevent.h"
|
||||
#include "events/pollevent.h"
|
||||
#include "events/widgetevent.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "messagecomponenttype.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "texthandler.h"
|
||||
@@ -35,82 +33,52 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
EventHandler::EventHandler(const NeoChatRoom *room, const RoomEvent *event)
|
||||
: m_room(room)
|
||||
, m_event(event)
|
||||
QString EventHandler::id(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "id called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
return !event->id().isEmpty() ? event->id() : event->transactionId();
|
||||
}
|
||||
|
||||
QString EventHandler::getId() const
|
||||
QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getId called with m_event set to nullptr.";
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "authorDisplayName called with room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "authorDisplayName called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
return !m_event->id().isEmpty() ? m_event->id() : m_event->transactionId();
|
||||
}
|
||||
|
||||
MessageComponentType::Type EventHandler::messageComponentType() const
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "messageComponentType called with m_event set to nullptr.";
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
|
||||
return MessageComponentType::typeForEvent(*m_event);
|
||||
}
|
||||
|
||||
Quotient::RoomMember EventHandler::getAuthor(bool isPending) const
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getAuthor called with m_room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getAuthor called with m_event set to nullptr. Returning empty user.";
|
||||
return {};
|
||||
}
|
||||
|
||||
return isPending ? m_room->localMember() : m_room->member(m_event->senderId());
|
||||
}
|
||||
|
||||
QString EventHandler::getAuthorDisplayName(bool isPending) const
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getAuthorDisplayName called with m_room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getAuthorDisplayName called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (is<RoomMemberEvent>(*m_event) && !m_event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].isNull()
|
||||
&& m_event->stateKey() == m_event->senderId()) {
|
||||
auto previousDisplayName = m_event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].toString().toHtmlEscaped();
|
||||
if (is<RoomMemberEvent>(*event) && !event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].isNull()
|
||||
&& event->stateKey() == event->senderId()) {
|
||||
auto previousDisplayName = event->unsignedJson()[QStringLiteral("prev_content")][QStringLiteral("displayname")].toString().toHtmlEscaped();
|
||||
if (previousDisplayName.isEmpty()) {
|
||||
previousDisplayName = m_event->senderId();
|
||||
previousDisplayName = event->senderId();
|
||||
}
|
||||
return previousDisplayName;
|
||||
} else {
|
||||
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
|
||||
const auto author = isPending ? room->localMember() : room->member(event->senderId());
|
||||
return author.htmlSafeDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
QString EventHandler::singleLineAuthorDisplayname(bool isPending) const
|
||||
QString EventHandler::singleLineAuthorDisplayname(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getAuthorDisplayName called with m_room set to nullptr.";
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "singleLineAuthorDisplayname called with room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getAuthorDisplayName called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "singleLineAuthorDisplayname called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
|
||||
const auto author = isPending ? room->localMember() : room->member(event->senderId());
|
||||
auto displayName = author.displayName();
|
||||
displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" "));
|
||||
displayName.replace(QStringLiteral("<br>"), QStringLiteral(" "));
|
||||
@@ -121,10 +89,10 @@ QString EventHandler::singleLineAuthorDisplayname(bool isPending) const
|
||||
return displayName;
|
||||
}
|
||||
|
||||
QDateTime EventHandler::getTime(bool isPending, QDateTime lastUpdated) const
|
||||
QDateTime EventHandler::time(const Quotient::RoomEvent *event, bool isPending, QDateTime lastUpdated)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getTime called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "time called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (isPending && lastUpdated == QDateTime()) {
|
||||
@@ -132,24 +100,16 @@ QDateTime EventHandler::getTime(bool isPending, QDateTime lastUpdated) const
|
||||
return {};
|
||||
}
|
||||
|
||||
return isPending ? lastUpdated : m_event->originTimestamp();
|
||||
return isPending ? lastUpdated : event->originTimestamp();
|
||||
}
|
||||
|
||||
QString EventHandler::getTimeString(bool relative, QLocale::FormatType format, bool isPending, QDateTime lastUpdated) const
|
||||
QString EventHandler::timeString(const Quotient::RoomEvent *event, bool relative, QLocale::FormatType format, bool isPending, QDateTime lastUpdated)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getTimeString called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (isPending && lastUpdated == QDateTime()) {
|
||||
qCWarning(EventHandling) << "a value must be provided for lastUpdated for a pending event.";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto ts = getTime(isPending, lastUpdated);
|
||||
auto ts = time(event, isPending, lastUpdated);
|
||||
if (ts.isValid()) {
|
||||
if (relative) {
|
||||
return m_format.formatRelativeDate(ts.toLocalTime().date(), format);
|
||||
KFormat formatter;
|
||||
return formatter.formatRelativeDate(ts.toLocalTime().date(), format);
|
||||
} else {
|
||||
return QLocale().toString(ts.toLocalTime().time(), format);
|
||||
}
|
||||
@@ -157,39 +117,45 @@ QString EventHandler::getTimeString(bool relative, QLocale::FormatType format, b
|
||||
return {};
|
||||
}
|
||||
|
||||
bool EventHandler::isHighlighted()
|
||||
QString EventHandler::timeString(const Quotient::RoomEvent *event, const QString &format, bool isPending, const QDateTime &lastUpdated)
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "isHighlighted called with m_room set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "isHighlighted called with m_event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return !m_room->isDirectChat() && m_room->isEventHighlighted(m_event);
|
||||
return time(event, isPending, lastUpdated).toLocalTime().toString(format);
|
||||
}
|
||||
|
||||
bool EventHandler::isHidden()
|
||||
bool EventHandler::isHighlighted(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "isHidden called with m_room set to nullptr.";
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "isHighlighted called with room set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "isHidden called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "isHighlighted called with event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_event->isStateEvent() && !NeoChatConfig::self()->showStateEvent()) {
|
||||
return !room->isDirectChat() && room->isEventHighlighted(event);
|
||||
}
|
||||
|
||||
bool EventHandler::isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "isHidden called with room set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "isHidden called with event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (event->isStateEvent() && !NeoChatConfig::self()->showStateEvent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(m_event)) {
|
||||
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
|
||||
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::self()->showLeaveJoinEvent()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::self()->showRename()) {
|
||||
} else if (roomMemberEvent->isRename() && roomMemberEvent->prevContent() && roomMemberEvent->prevContent()->membership == roomMemberEvent->membership()
|
||||
&& !NeoChatConfig::self()->showRename()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave()
|
||||
&& !NeoChatConfig::self()->showAvatarUpdate()) {
|
||||
@@ -197,33 +163,33 @@ bool EventHandler::isHidden()
|
||||
}
|
||||
}
|
||||
|
||||
if (m_event->isStateEvent() && eventCast<const StateEvent>(m_event)->repeatsState()) {
|
||||
if (event->isStateEvent() && eventCast<const StateEvent>(event)->repeatsState()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// isReplacement?
|
||||
if (auto e = eventCast<const RoomMessageEvent>(m_event)) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (!e->replacedEvent().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is<RedactionEvent>(*m_event) || is<ReactionEvent>(*m_event)) {
|
||||
if (is<RedactionEvent>(*event) || is<ReactionEvent>(*event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto e = eventCast<const RoomMessageEvent>(m_event)) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (!e->replacedEvent().isEmpty() && e->replacedEvent() != e->id()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_room->connection()->isIgnored(m_event->senderId())) {
|
||||
if (room->connection()->isIgnored(event->senderId())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// hide ending live location beacons
|
||||
if (m_event->isStateEvent() && m_event->matrixType() == "org.matrix.msc3672.beacon_info"_ls && !m_event->contentJson()["live"_ls].toBool()) {
|
||||
if (event->isStateEvent() && event->matrixType() == "org.matrix.msc3672.beacon_info"_ls && !event->contentJson()["live"_ls].toBool()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -262,44 +228,52 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
return body;
|
||||
}
|
||||
|
||||
QString EventHandler::getRichBody(bool stripNewlines) const
|
||||
QString EventHandler::richBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getRichBody called with m_event set to nullptr.";
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "richBody called with room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return getBody(m_event, Qt::RichText, stripNewlines);
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "richBody called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return getBody(room, event, Qt::RichText, stripNewlines);
|
||||
}
|
||||
|
||||
QString EventHandler::getPlainBody(bool stripNewlines) const
|
||||
QString EventHandler::plainBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getPlainBody called with m_event set to nullptr.";
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "plainBody called with room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return getBody(m_event, Qt::PlainText, stripNewlines);
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "plainBody called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return getBody(room, event, Qt::PlainText, stripNewlines);
|
||||
}
|
||||
|
||||
QString EventHandler::getMarkdownBody() const
|
||||
QString EventHandler::markdownBody(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getMarkdownBody called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "markdownBody called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!m_event->is<RoomMessageEvent>()) {
|
||||
qCWarning(EventHandling) << "getMarkdownBody called when m_event isn't a RoomMessageEvent.";
|
||||
if (!event->is<RoomMessageEvent>()) {
|
||||
qCWarning(EventHandling) << "markdownBody called when event isn't a RoomMessageEvent.";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
|
||||
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
|
||||
|
||||
QString plainBody = roomMessageEvent->plainBody();
|
||||
plainBody.remove(TextRegex::removeReply);
|
||||
return plainBody;
|
||||
}
|
||||
|
||||
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
|
||||
QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines)
|
||||
{
|
||||
if (event->isRedacted()) {
|
||||
auto reason = event->redactedBecause()->reason();
|
||||
@@ -310,15 +284,15 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
||||
|
||||
return switchOnType(
|
||||
*event,
|
||||
[this, format, stripNewlines](const RoomMessageEvent &event) {
|
||||
return getMessageBody(event, format, stripNewlines);
|
||||
[room, format, stripNewlines](const RoomMessageEvent &event) {
|
||||
return getMessageBody(room, event, format, stripNewlines);
|
||||
},
|
||||
[](const StickerEvent &e) {
|
||||
return e.body();
|
||||
},
|
||||
[this, prettyPrint](const RoomMemberEvent &e) {
|
||||
[room, prettyPrint](const RoomMemberEvent &e) {
|
||||
// FIXME: Rewind to the name that was at the time of this event
|
||||
auto subjectName = m_room->member(e.userId()).htmlSafeDisplayName();
|
||||
auto subjectName = room->member(e.userId()).htmlSafeDisplayName();
|
||||
if (e.membership() == Membership::Leave) {
|
||||
if (e.prevContent() && e.prevContent()->displayName) {
|
||||
subjectName = sanitized(*e.prevContent()->displayName);
|
||||
@@ -330,7 +304,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
||||
|
||||
if (prettyPrint) {
|
||||
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
|
||||
.arg(e.userId(), m_room->member(e.userId()).color().name(), subjectName);
|
||||
.arg(e.userId(), room->member(e.userId()).color().name(), subjectName);
|
||||
}
|
||||
|
||||
// The below code assumes senderName output in AuthorRole
|
||||
@@ -393,9 +367,13 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
||||
if (e.prevContent() && e.prevContent()->membership == Membership::Ban) {
|
||||
return (e.senderId() != e.userId()) ? i18n("unbanned %1", subjectName) : i18n("self-unbanned");
|
||||
}
|
||||
return (e.senderId() != e.userId())
|
||||
? i18n("has put %1 out of the room: %2", subjectName, e.contentJson()["reason"_ls].toString().toHtmlEscaped())
|
||||
: i18n("left the room");
|
||||
if (e.senderId() == e.userId()) {
|
||||
return i18n("left the room");
|
||||
}
|
||||
if (const auto &reason = e.contentJson()["reason"_ls].toString().toHtmlEscaped(); !reason.isEmpty()) {
|
||||
return i18n("has put %1 out of the room: %2", subjectName, reason);
|
||||
}
|
||||
return i18n("has put %1 out of the room", subjectName);
|
||||
case Membership::Ban:
|
||||
if (e.senderId() != e.userId()) {
|
||||
if (e.reason().isEmpty()) {
|
||||
@@ -466,7 +444,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
QString EventHandler::getMessageBody(const RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines) const
|
||||
QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines)
|
||||
{
|
||||
TextHandler textHandler;
|
||||
|
||||
@@ -498,24 +476,24 @@ QString EventHandler::getMessageBody(const RoomMessageEvent &event, Qt::TextForm
|
||||
}
|
||||
|
||||
if (format == Qt::RichText) {
|
||||
return textHandler.handleRecieveRichText(inputFormat, m_room, &event, stripNewlines, event.isReplaced());
|
||||
return textHandler.handleRecieveRichText(inputFormat, room, &event, stripNewlines, event.isReplaced());
|
||||
} else {
|
||||
return textHandler.handleRecievePlainText(inputFormat, stripNewlines);
|
||||
}
|
||||
}
|
||||
|
||||
QString EventHandler::getGenericBody() const
|
||||
QString EventHandler::genericBody(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getGenericBody called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "genericBody called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event->isRedacted()) {
|
||||
if (event->isRedacted()) {
|
||||
return i18n("<i>[This message was deleted]</i>");
|
||||
}
|
||||
|
||||
return switchOnType(
|
||||
*m_event,
|
||||
*event,
|
||||
[](const RoomMessageEvent &e) {
|
||||
Q_UNUSED(e)
|
||||
return i18n("sent a message");
|
||||
@@ -635,29 +613,33 @@ QString EventHandler::getGenericBody() const
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
QString EventHandler::subtitleText() const
|
||||
QString EventHandler::subtitleText(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "subtitleText called with m_event set to nullptr.";
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "subtitleText called with room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return singleLineAuthorDisplayname() + (m_event->isStateEvent() ? QLatin1String(" ") : QLatin1String(": ")) + getPlainBody(true);
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "subtitleText called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return singleLineAuthorDisplayname(room, event) + (event->isStateEvent() ? QLatin1String(" ") : QLatin1String(": ")) + plainBody(room, event, true);
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfo() const
|
||||
QVariantMap EventHandler::mediaInfo(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getMediaInfo called with m_room set to nullptr.";
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "mediaInfo called with room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getMediaInfo called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "mediaInfo called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return getMediaInfoForEvent(m_event);
|
||||
return getMediaInfoForEvent(room, event);
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event) const
|
||||
QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
{
|
||||
QString eventId = event->id();
|
||||
|
||||
@@ -670,7 +652,7 @@ QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event)
|
||||
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
fileInfo = roomMessageEvent->content()->fileInfo();
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(fileInfo, eventId, false, false);
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, 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;
|
||||
@@ -682,13 +664,17 @@ QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event)
|
||||
auto stickerEvent = eventCast<const StickerEvent>(event);
|
||||
fileInfo = &stickerEvent->image();
|
||||
|
||||
return getMediaInfoFromFileInfo(fileInfo, eventId, false, true);
|
||||
return getMediaInfoFromFileInfo(room, fileInfo, eventId, false, true);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail, bool isSticker) const
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
const EventContent::FileInfo *fileInfo,
|
||||
const QString &eventId,
|
||||
bool isThumbnail,
|
||||
bool isSticker)
|
||||
{
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
@@ -696,7 +682,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
|
||||
if (!fileInfo->url().isValid() || fileInfo->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
QUrl source = m_room->makeMediaUrl(eventId, fileInfo->url());
|
||||
QUrl source = room->makeMediaUrl(eventId, fileInfo->url());
|
||||
|
||||
if (source.isValid()) {
|
||||
mediaInfo["source"_ls] = source;
|
||||
@@ -728,7 +714,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(room, castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
@@ -751,7 +737,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(room, castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
@@ -775,156 +761,89 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
|
||||
return mediaInfo;
|
||||
}
|
||||
|
||||
bool EventHandler::hasReply() const
|
||||
bool EventHandler::hasReply(const Quotient::RoomEvent *event, bool showFallbacks)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "hasReply called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "hasReply called with event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
return !m_event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString().isEmpty();
|
||||
|
||||
const auto relations = event->contentPart<QJsonObject>("m.relates_to"_ls);
|
||||
if (!relations.isEmpty()) {
|
||||
const bool hasReplyRelation = relations.contains("m.in_reply_to"_ls);
|
||||
bool isFallingBack = relations["is_falling_back"_ls].toBool();
|
||||
return hasReplyRelation && (showFallbacks ? true : !isFallingBack);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QString EventHandler::getReplyId() const
|
||||
QString EventHandler::replyId(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyId called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "replyId called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return m_event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
return event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
}
|
||||
|
||||
MessageComponentType::Type EventHandler::replyMessageComponentType() const
|
||||
Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "replyMessageComponentType called with m_room set to nullptr.";
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "replyMessageComponentType called with m_event set to nullptr.";
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
|
||||
auto replyEvent = m_room->getReplyForEvent(*m_event);
|
||||
if (replyEvent == nullptr) {
|
||||
return MessageComponentType::Other;
|
||||
}
|
||||
return MessageComponentType::typeForEvent(*replyEvent);
|
||||
}
|
||||
|
||||
Quotient::RoomMember EventHandler::getReplyAuthor() const
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyAuthor called with m_room set to nullptr.";
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "replyAuthor called with room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "replyAuthor called with event set to nullptr. Returning empty user.";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto replyPtr = m_room->getReplyForEvent(*m_event)) {
|
||||
return m_room->member(replyPtr->senderId());
|
||||
if (auto replyPtr = room->getReplyForEvent(*event)) {
|
||||
return room->member(replyPtr->senderId());
|
||||
} else {
|
||||
return m_room->member(QString());
|
||||
return room->member(QString());
|
||||
}
|
||||
}
|
||||
|
||||
QString EventHandler::getReplyRichBody(bool stripNewlines) const
|
||||
bool EventHandler::isThreaded(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyRichBody called with m_room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyRichBody called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto replyEvent = m_room->getReplyForEvent(*m_event);
|
||||
if (replyEvent == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return getBody(replyEvent, Qt::RichText, stripNewlines);
|
||||
}
|
||||
|
||||
QString EventHandler::getReplyPlainBody(bool stripNewlines) const
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyPlainBody called with m_room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyPlainBody called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto replyEvent = m_room->getReplyForEvent(*m_event);
|
||||
if (replyEvent == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return getBody(replyEvent, Qt::PlainText, stripNewlines);
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getReplyMediaInfo() const
|
||||
{
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyMediaInfo called with m_room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyMediaInfo called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
auto replyPtr = m_room->getReplyForEvent(*m_event);
|
||||
if (!replyPtr) {
|
||||
return {};
|
||||
}
|
||||
return getMediaInfoForEvent(replyPtr);
|
||||
}
|
||||
|
||||
bool EventHandler::isThreaded() const
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "isThreaded called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "isThreaded called with event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
|
||||
return (m_event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||
&& m_event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls)
|
||||
|| (!m_event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && m_event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls));
|
||||
return (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls)
|
||||
|| (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls));
|
||||
}
|
||||
|
||||
QString EventHandler::threadRoot() const
|
||||
QString EventHandler::threadRoot(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "threadRoot called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "threadRoot called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get the thread root ID from m.relates_to if it exists.
|
||||
if (m_event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||
&& m_event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls) {
|
||||
return m_event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString();
|
||||
if (event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||
&& event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls) {
|
||||
return event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString();
|
||||
}
|
||||
// For thread root events they have an m.relations in the unsigned part with a m.thread object.
|
||||
// If so return the event ID as it is the root.
|
||||
if (!m_event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && m_event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
|
||||
return getId();
|
||||
if (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
|
||||
return id(event);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float EventHandler::getLatitude() const
|
||||
float EventHandler::latitude(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getLatitude called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "latitude called with event set to nullptr.";
|
||||
return -100.0;
|
||||
}
|
||||
|
||||
const auto geoUri = m_event->contentJson()["geo_uri"_ls].toString();
|
||||
const auto geoUri = event->contentJson()["geo_uri"_ls].toString();
|
||||
if (geoUri.isEmpty()) {
|
||||
return -100.0; // latitude runs from -90deg to +90deg so -100 is out of range.
|
||||
}
|
||||
@@ -932,14 +851,14 @@ float EventHandler::getLatitude() const
|
||||
return latitude.toFloat();
|
||||
}
|
||||
|
||||
float EventHandler::getLongitude() const
|
||||
float EventHandler::longitude(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getLongitude called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "longitude called with event set to nullptr.";
|
||||
return -200.0;
|
||||
}
|
||||
|
||||
const auto geoUri = m_event->contentJson()["geo_uri"_ls].toString();
|
||||
const auto geoUri = event->contentJson()["geo_uri"_ls].toString();
|
||||
if (geoUri.isEmpty()) {
|
||||
return -200.0; // longitude runs from -180deg to +180deg so -200 is out of range.
|
||||
}
|
||||
@@ -947,14 +866,14 @@ float EventHandler::getLongitude() const
|
||||
return latitude.toFloat();
|
||||
}
|
||||
|
||||
QString EventHandler::getLocationAssetType() const
|
||||
QString EventHandler::locationAssetType(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getLocationAssetType called with m_event set to nullptr.";
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "locationAssetType called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto assetType = m_event->contentJson()["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
||||
const auto assetType = event->contentJson()["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
||||
if (assetType.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -3,24 +3,22 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <KFormat>
|
||||
|
||||
#include <Quotient/eventitem.h>
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
namespace EventContent
|
||||
{
|
||||
class FileInfo;
|
||||
}
|
||||
class RoomEvent;
|
||||
class RoomMember;
|
||||
class RoomMessageEvent;
|
||||
}
|
||||
|
||||
class LinkPreviewer;
|
||||
class NeoChatRoom;
|
||||
class ReactionModel;
|
||||
|
||||
/**
|
||||
* @class EventHandler
|
||||
@@ -38,40 +36,19 @@ class ReactionModel;
|
||||
*/
|
||||
class EventHandler
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
EventHandler(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the Matrix ID of the event.
|
||||
* @brief Return the ID of the event.
|
||||
*
|
||||
* Returns the transaction ID if the Matrix ID is empty, which may be the case
|
||||
* for a pending event.
|
||||
*/
|
||||
QString getId() const;
|
||||
|
||||
/**
|
||||
* @brief The MessageComponentType to use to visualise the main event content.
|
||||
*/
|
||||
MessageComponentType::Type messageComponentType() const;
|
||||
|
||||
/**
|
||||
* @brief Get the author of the event in context of the room.
|
||||
*
|
||||
* An empty Quotient::RoomMember will be returned if the EventHandler hasn't had
|
||||
* the room or event initialised.
|
||||
*
|
||||
* @param isPending if the event is pending, i.e. has not been confirmed by
|
||||
* the server.
|
||||
*
|
||||
* @return a Quotient::RoomMember object for the user.
|
||||
*
|
||||
* @sa Quotient::RoomMember
|
||||
*/
|
||||
Quotient::RoomMember getAuthor(bool isPending = false) const;
|
||||
static QString id(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @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
|
||||
* 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.
|
||||
@@ -79,7 +56,7 @@ public:
|
||||
* @param isPending whether the event is pending as this cannot be derived from
|
||||
* just the event object.
|
||||
*/
|
||||
QString getAuthorDisplayName(bool isPending = false) const;
|
||||
static QString authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);
|
||||
|
||||
/**
|
||||
* @brief Get the display name of the event author but with any newlines removed.
|
||||
@@ -90,12 +67,12 @@ public:
|
||||
* @param isPending whether the event is pending as this cannot be derived from
|
||||
* just the event object.
|
||||
*/
|
||||
QString singleLineAuthorDisplayname(bool isPending = false) const;
|
||||
static QString singleLineAuthorDisplayname(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);
|
||||
|
||||
/**
|
||||
* @brief Return a QDateTime object for the event timestamp.
|
||||
*/
|
||||
QDateTime getTime(bool isPending = false, QDateTime lastUpdated = {}) const;
|
||||
static QDateTime time(const Quotient::RoomEvent *event, bool isPending = false, QDateTime lastUpdated = {});
|
||||
|
||||
/**
|
||||
* @brief Return a QString for the event timestamp.
|
||||
@@ -111,14 +88,32 @@ public:
|
||||
* @param lastUpdated the time the event was last updated locally as this cannot be
|
||||
* obtained from the event.
|
||||
*/
|
||||
QString getTimeString(bool relative, QLocale::FormatType format = QLocale::ShortFormat, bool isPending = false, QDateTime lastUpdated = {}) const;
|
||||
static QString timeString(const Quotient::RoomEvent *event,
|
||||
bool relative,
|
||||
QLocale::FormatType format = QLocale::ShortFormat,
|
||||
bool isPending = false,
|
||||
QDateTime lastUpdated = {});
|
||||
|
||||
/**
|
||||
* @brief Return a QString for the event timestamp.
|
||||
*
|
||||
* This is intended to return a string that is read for display in the UI without
|
||||
* any further manipulation required.
|
||||
*
|
||||
* @param format the format to use as a string.
|
||||
* @param isPending whether the event is pending as this cannot be derived from
|
||||
* just the event object.
|
||||
* @param lastUpdated the time the event was last updated locally as this cannot be
|
||||
* obtained from the event.
|
||||
*/
|
||||
static QString timeString(const Quotient::RoomEvent *event, const QString &format, bool isPending = false, const QDateTime &lastUpdated = {});
|
||||
|
||||
/**
|
||||
* @brief Whether the event should be highlighted in the timeline.
|
||||
*
|
||||
* @note Messages in direct chats are never highlighted.
|
||||
*/
|
||||
bool isHighlighted();
|
||||
static bool isHighlighted(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Whether the event should be hidden in the timeline.
|
||||
@@ -127,7 +122,7 @@ public:
|
||||
* user has hidden all state events or if the sender has been ignored by the local
|
||||
* user.
|
||||
*/
|
||||
bool isHidden();
|
||||
static bool isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief The input format of the body in the message.
|
||||
@@ -159,7 +154,7 @@ public:
|
||||
*
|
||||
* @param stripNewlines whether the output should have new lines in it.
|
||||
*/
|
||||
QString getRichBody(bool stripNewlines = false) const;
|
||||
static QString richBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines = false);
|
||||
|
||||
/**
|
||||
* @brief Output a string for the message content ready for display in a plain text field.
|
||||
@@ -175,14 +170,14 @@ public:
|
||||
*
|
||||
* @param stripNewlines whether the output should have new lines in it.
|
||||
*/
|
||||
QString getPlainBody(bool stripNewlines = false) const;
|
||||
static QString plainBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines = false);
|
||||
|
||||
/**
|
||||
* @brief Output the original body for the message content, useful for editing the original message.
|
||||
*
|
||||
* The event type must be a room message event.
|
||||
*/
|
||||
QString getMarkdownBody() const;
|
||||
static QString markdownBody(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Output a generic string for the message content ready for display.
|
||||
@@ -195,9 +190,9 @@ public:
|
||||
* E.g. For a message the text will be:
|
||||
* "sent a message"
|
||||
*
|
||||
* @sa getRichBody(), getPlainBody()
|
||||
* @sa richBody(), plainBody()
|
||||
*/
|
||||
QString getGenericBody() const;
|
||||
static QString genericBody(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Output a string for the event to be used as a RoomList subtitle.
|
||||
@@ -205,7 +200,7 @@ public:
|
||||
* The output includes the username followed by the plain message, all with no
|
||||
* line breaks.
|
||||
*/
|
||||
QString subtitleText() const;
|
||||
static QString subtitleText(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the media info for the event.
|
||||
@@ -223,22 +218,21 @@ public:
|
||||
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
|
||||
* - isSticker - Whether the image is a sticker or not
|
||||
*/
|
||||
QVariantMap getMediaInfo() const;
|
||||
static QVariantMap mediaInfo(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Whether the event is a reply to another in the timeline.
|
||||
*
|
||||
* @param showFallbacks whether message that have is_falling_back set true should
|
||||
* show the fallback reply. Leave true for non-threaded
|
||||
* timelines.
|
||||
*/
|
||||
bool hasReply() const;
|
||||
static bool hasReply(const Quotient::RoomEvent *event, bool showFallbacks = true);
|
||||
|
||||
/**
|
||||
* @brief Return the Matrix ID of the event replied to.
|
||||
*/
|
||||
QString getReplyId() const;
|
||||
|
||||
/**
|
||||
* @brief The MessageComponentType to use to visualise the reply content.
|
||||
*/
|
||||
MessageComponentType::Type replyMessageComponentType() const;
|
||||
static QString replyId(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Get the author of the event replied to in context of the room.
|
||||
@@ -253,73 +247,21 @@ public:
|
||||
*
|
||||
* @sa Quotient::RoomMember
|
||||
*/
|
||||
Quotient::RoomMember getReplyAuthor() const;
|
||||
|
||||
/**
|
||||
* @brief Output a string for the message content of the event replied to ready
|
||||
* for display in a rich text field.
|
||||
*
|
||||
* The output string is dependant upon the event type and the desired output format.
|
||||
*
|
||||
* For most messages this is the body content of the message. For media messages
|
||||
* this will be the caption and for state events it will be a string specific
|
||||
* to that event with some dynamic details about the event added.
|
||||
*
|
||||
* E.g. For a room topic state event the text will be:
|
||||
* "set the topic to: <new topic text>"
|
||||
*
|
||||
* @param stripNewlines whether the output should have new lines in it.
|
||||
*/
|
||||
QString getReplyRichBody(bool stripNewlines = false) const;
|
||||
|
||||
/**
|
||||
* @brief Output a string for the message content of the event replied to ready
|
||||
* for display in a plain text field.
|
||||
*
|
||||
* The output string is dependant upon the event type and the desired output format.
|
||||
*
|
||||
* For most messages this is the body content of the message. For media messages
|
||||
* this will be the caption and for state events it will be a string specific
|
||||
* to that event with some dynamic details about the event added.
|
||||
*
|
||||
* E.g. For a room topic state event the text will be:
|
||||
* "set the topic to: <new topic text>"
|
||||
*
|
||||
* @param stripNewlines whether the output should have new lines in it.
|
||||
*/
|
||||
QString getReplyPlainBody(bool stripNewlines = false) const;
|
||||
|
||||
/**
|
||||
* @brief Return the media info for the event replied to.
|
||||
*
|
||||
* An empty QVariantMap will be returned for any event that doesn't have any
|
||||
* media info.
|
||||
*
|
||||
* @return This should consist of the following:
|
||||
* - source - The mxc URL for the media.
|
||||
* - mimeType - The MIME type of the media (should be image/xxx for this delegate).
|
||||
* - mimeIcon - The MIME icon name (should be image-xxx).
|
||||
* - size - The file size in bytes.
|
||||
* - width - The width in pixels of the audio media.
|
||||
* - height - The height in pixels of the audio media.
|
||||
* - tempInfo - mediaInfo (with the same properties as this except no tempInfo) for a temporary image while the file downloads.
|
||||
* - isSticker - Whether the image is a sticker or not
|
||||
*/
|
||||
QVariantMap getReplyMediaInfo() const;
|
||||
static Quotient::RoomMember replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Whether the message is part of a thread.
|
||||
*
|
||||
* i.e. There is a rel_type of m.thread.
|
||||
*/
|
||||
bool isThreaded() const;
|
||||
static bool isThreaded(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the Matrix ID of the thread's root message.
|
||||
*
|
||||
* Empty if this not part of a thread.
|
||||
*/
|
||||
QString threadRoot() const;
|
||||
static QString threadRoot(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the latitude for the event.
|
||||
@@ -327,7 +269,7 @@ public:
|
||||
* Returns -100.0 if the event doesn't have a location (latitudes are in the
|
||||
* range -90deg to +90deg so -100 is out of range).
|
||||
*/
|
||||
float getLatitude() const;
|
||||
static float latitude(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the longitude for the event.
|
||||
@@ -335,23 +277,21 @@ public:
|
||||
* Returns -200.0 if the event doesn't have a location (latitudes are in the
|
||||
* range -180deg to +180deg so -200 is out of range).
|
||||
*/
|
||||
float getLongitude() const;
|
||||
static float longitude(const Quotient::RoomEvent *event);
|
||||
|
||||
/**
|
||||
* @brief Return the type of location marker for the event.
|
||||
*/
|
||||
QString getLocationAssetType() const;
|
||||
static QString locationAssetType(const Quotient::RoomEvent *event);
|
||||
|
||||
private:
|
||||
const NeoChatRoom *m_room = nullptr;
|
||||
const Quotient::RoomEvent *m_event = nullptr;
|
||||
static QString getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines);
|
||||
static QString getMessageBody(const NeoChatRoom *room, const Quotient::RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines);
|
||||
|
||||
KFormat m_format;
|
||||
|
||||
QString getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const;
|
||||
QString getMessageBody(const Quotient::RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines) const;
|
||||
|
||||
QVariantMap getMediaInfoForEvent(const Quotient::RoomEvent *event) const;
|
||||
QVariantMap
|
||||
getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false, bool isSticker = false) const;
|
||||
static QVariantMap getMediaInfoForEvent(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
QVariantMap static getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
const Quotient::EventContent::FileInfo *fileInfo,
|
||||
const QString &eventId,
|
||||
bool isThumbnail = false,
|
||||
bool isSticker = false);
|
||||
};
|
||||
|
||||
@@ -10,22 +10,13 @@
|
||||
#include <Quotient/keyverificationsession.h>
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
#include <Quotient/keyimport.h>
|
||||
#endif
|
||||
|
||||
#include "controller.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
struct ForeignConfig {
|
||||
Q_GADGET
|
||||
QML_FOREIGN(NeoChatConfig)
|
||||
QML_NAMED_ELEMENT(Config)
|
||||
QML_SINGLETON
|
||||
public:
|
||||
static NeoChatConfig *create(QQmlEngine *, QJSEngine *)
|
||||
{
|
||||
QQmlEngine::setObjectOwnership(NeoChatConfig::self(), QQmlEngine::CppOwnership);
|
||||
return NeoChatConfig::self();
|
||||
}
|
||||
};
|
||||
|
||||
struct ForeignAccountRegistry {
|
||||
Q_GADGET
|
||||
QML_FOREIGN(Quotient::AccountRegistry)
|
||||
@@ -52,8 +43,11 @@ struct ForeignSSSSHandler {
|
||||
QML_NAMED_ELEMENT(SSSSHandler)
|
||||
};
|
||||
|
||||
struct RoomMemberForeign {
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
struct ForeignKeyImport {
|
||||
Q_GADGET
|
||||
QML_FOREIGN(Quotient::RoomMember)
|
||||
QML_NAMED_ELEMENT(RoomMember)
|
||||
QML_SINGLETON
|
||||
QML_FOREIGN(Quotient::KeyImport)
|
||||
QML_NAMED_ELEMENT(KeyImport)
|
||||
};
|
||||
#endif
|
||||
|
||||
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);
|
||||
};
|
||||
@@ -40,7 +40,7 @@ float LocationHelper::zoomToFit(const QRectF &r, float mapWidth, float mapHeight
|
||||
const auto zy = std::log2((mapHeight / (p2.y() - p1.y())));
|
||||
const auto z = std::min(zx, zy);
|
||||
|
||||
return std::clamp(z, 5.0, 18.0);
|
||||
return z;
|
||||
}
|
||||
|
||||
#include "moc_locationhelper.cpp"
|
||||
|
||||
@@ -78,9 +78,7 @@ void LoginHelper::init()
|
||||
account.setHomeserver(m_connection->homeserver());
|
||||
account.setDeviceId(m_connection->deviceId());
|
||||
account.setDeviceName(m_deviceName);
|
||||
if (!Controller::instance().saveAccessTokenToKeyChain(account.userId(), m_connection->accessToken())) {
|
||||
qWarning() << "Couldn't save access token";
|
||||
}
|
||||
Controller::instance().saveAccessTokenToKeyChain(account.userId(), m_connection->accessToken());
|
||||
account.sync();
|
||||
Controller::instance().addConnection(m_connection);
|
||||
Controller::instance().setActiveConnection(m_connection);
|
||||
|
||||
@@ -11,7 +11,7 @@ import org.kde.kirigamiaddons.formcard as FormCard
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.settings
|
||||
|
||||
FormCard.FormCardPage {
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
|
||||
property bool showExisting: false
|
||||
@@ -23,202 +23,237 @@ FormCard.FormCardPage {
|
||||
signal connectionChosen
|
||||
|
||||
title: i18n("Welcome")
|
||||
globalToolBarStyle: Kirigami.ApplicationHeaderStyle.None
|
||||
|
||||
header: QQC2.Control {
|
||||
contentItem: Kirigami.InlineMessage {
|
||||
id: headerMessage
|
||||
type: Kirigami.MessageType.Error
|
||||
showCloseButton: true
|
||||
visible: false
|
||||
}
|
||||
}
|
||||
topPadding: 0
|
||||
bottomPadding: 0
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
|
||||
implicitHeight: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
|
||||
}
|
||||
contentItem: ColumnLayout {
|
||||
spacing: 0
|
||||
|
||||
Kirigami.Heading {
|
||||
id: welcomeMessage
|
||||
|
||||
text: i18n("Welcome to NeoChat")
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
id: existingAccountsHeader
|
||||
title: i18nc("@title", "Continue with an existing account")
|
||||
visible: (loadedAccounts.count > 0 || loadingAccounts.count > 0) && root._showExisting
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
visible: existingAccountsHeader.visible
|
||||
Repeater {
|
||||
id: loadedAccounts
|
||||
model: AccountRegistry
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: model.userId
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection;
|
||||
root.connectionChosen();
|
||||
}
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
id: loadingAccounts
|
||||
model: Controller.accountsLoading
|
||||
delegate: FormCard.AbstractFormDelegate {
|
||||
id: loadingDelegate
|
||||
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
Kirigami.InlineMessage {
|
||||
id: headerMessage
|
||||
type: Kirigami.MessageType.Error
|
||||
showCloseButton: true
|
||||
visible: false
|
||||
|
||||
background: null
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: i18nc("As in 'this account is still loading'", "%1 (loading)", modelData)
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
Accessible.ignored: true // base class sets this text on root already
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
text: i18nc("@action:button", "Log out of this account")
|
||||
icon.name: "edit-delete-remove"
|
||||
onClicked: Controller.removeConnection(modelData)
|
||||
display: QQC2.Button.IconOnly
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
enabled: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
}
|
||||
|
||||
FormCard.FormArrow {
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
direction: Qt.RightArrow
|
||||
visible: root.background.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
onCountChanged: {
|
||||
if (loadingAccounts.count === 0 && loadedAccounts.count === 1 && showExisting) {
|
||||
Controller.activeConnection = AccountRegistry.data(AccountRegistry.index(0, 0), 257);
|
||||
root.connectionChosen();
|
||||
}
|
||||
Layout.fillWidth: true
|
||||
Layout.margins: Kirigami.Units.largeSpacing
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Log in or Create a New Account")
|
||||
}
|
||||
contentItem: Item {
|
||||
ColumnLayout {
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
verticalCenter: parent.verticalCenter
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Loader {
|
||||
id: module
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: Qt.createComponent('org.kde.neochat.login', root.initialStep)
|
||||
spacing: 0
|
||||
|
||||
Connections {
|
||||
id: stepConnections
|
||||
target: currentStep
|
||||
Kirigami.Icon {
|
||||
source: "org.kde.neochat"
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
implicitWidth: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
|
||||
implicitHeight: Math.round(Kirigami.Units.iconSizes.huge * 1.5)
|
||||
}
|
||||
|
||||
function onProcessed(nextStep: string): void {
|
||||
module.source = nextStep + ".qml";
|
||||
root.currentStepString = nextStep;
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
if (!module.item.noControls) {
|
||||
module.item.forceActiveFocus();
|
||||
} else {
|
||||
continueButton.forceActiveFocus();
|
||||
Kirigami.Heading {
|
||||
id: welcomeMessage
|
||||
|
||||
text: i18n("NeoChat")
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
FormCard.FormHeader {
|
||||
id: existingAccountsHeader
|
||||
title: i18nc("@title", "Continue with an existing account")
|
||||
visible: (loadedAccounts.count > 0 || loadingAccounts.count > 0) && root._showExisting
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
visible: existingAccountsHeader.visible
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
Repeater {
|
||||
id: loadedAccounts
|
||||
model: AccountRegistry
|
||||
delegate: FormCard.FormButtonDelegate {
|
||||
text: model.userId
|
||||
onClicked: {
|
||||
Controller.activeConnection = model.connection;
|
||||
root.connectionChosen();
|
||||
}
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
id: loadingAccounts
|
||||
model: Controller.accountsLoading
|
||||
delegate: FormCard.AbstractFormDelegate {
|
||||
id: loadingDelegate
|
||||
|
||||
function onShowMessage(message: string): void {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
topPadding: Kirigami.Units.smallSpacing
|
||||
bottomPadding: Kirigami.Units.smallSpacing
|
||||
|
||||
function onClearError(): void {
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
}
|
||||
background: null
|
||||
contentItem: RowLayout {
|
||||
spacing: 0
|
||||
|
||||
function onCloseDialog(): void {
|
||||
root.closeDialog();
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: i18nc("As in 'this account is still loading'", "%1 (loading)", modelData)
|
||||
elide: Text.ElideRight
|
||||
wrapMode: Text.Wrap
|
||||
maximumLineCount: 2
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
Accessible.ignored: true // base class sets this text on root already
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
text: i18nc("@action:button", "Log out of this account")
|
||||
icon.name: "im-kick-user"
|
||||
onClicked: Controller.removeConnection(modelData)
|
||||
display: QQC2.Button.IconOnly
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
enabled: true
|
||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||
}
|
||||
|
||||
FormCard.FormArrow {
|
||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
|
||||
direction: Qt.RightArrow
|
||||
visible: root.background.visible
|
||||
}
|
||||
}
|
||||
}
|
||||
onCountChanged: {
|
||||
if (loadingAccounts.count === 0 && loadedAccounts.count === 1 && showExisting) {
|
||||
Controller.activeConnection = AccountRegistry.data(AccountRegistry.index(0, 0), 257);
|
||||
root.connectionChosen();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: Registration
|
||||
function onNextStepChanged() {
|
||||
if (Registration.nextStep === "m.login.recaptcha") {
|
||||
stepConnections.onProcessed("Captcha");
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("@title", "Log in or Create a New Account")
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
Loader {
|
||||
id: module
|
||||
Layout.fillWidth: true
|
||||
sourceComponent: Qt.createComponent('org.kde.neochat.login', root.initialStep)
|
||||
|
||||
Connections {
|
||||
id: stepConnections
|
||||
target: currentStep
|
||||
|
||||
function onProcessed(nextStep: string): void {
|
||||
module.source = nextStep + ".qml";
|
||||
root.currentStepString = nextStep;
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
if (!module.item.noControls) {
|
||||
module.item.forceActiveFocus();
|
||||
} else {
|
||||
continueButton.forceActiveFocus();
|
||||
}
|
||||
}
|
||||
|
||||
function onShowMessage(message: string): void {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = true;
|
||||
headerMessage.type = Kirigami.MessageType.Information;
|
||||
}
|
||||
|
||||
function onClearError(): void {
|
||||
headerMessage.text = "";
|
||||
headerMessage.visible = false;
|
||||
}
|
||||
|
||||
function onCloseDialog(): void {
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
if (Registration.nextStep === "m.login.terms") {
|
||||
stepConnections.onProcessed("Terms");
|
||||
|
||||
Connections {
|
||||
target: Registration
|
||||
|
||||
function onNextStepChanged() {
|
||||
if (Registration.nextStep === "m.login.recaptcha") {
|
||||
stepConnections.onProcessed("Captcha");
|
||||
}
|
||||
if (Registration.nextStep === "m.login.terms") {
|
||||
stepConnections.onProcessed("Terms");
|
||||
}
|
||||
if (Registration.nextStep === "m.login.email.identity") {
|
||||
stepConnections.onProcessed("Email");
|
||||
}
|
||||
if (Registration.nextStep === "loading") {
|
||||
stepConnections.onProcessed("Loading");
|
||||
}
|
||||
}
|
||||
}
|
||||
if (Registration.nextStep === "m.login.email.identity") {
|
||||
stepConnections.onProcessed("Email");
|
||||
}
|
||||
if (Registration.nextStep === "loading") {
|
||||
stepConnections.onProcessed("Loading");
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = message.length > 0;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Connections {
|
||||
target: LoginHelper
|
||||
function onErrorOccured(message) {
|
||||
headerMessage.text = message;
|
||||
headerMessage.visible = message.length > 0;
|
||||
headerMessage.type = Kirigami.MessageType.Error;
|
||||
|
||||
FormCard.FormDelegateSeparator {
|
||||
below: continueButton
|
||||
visible: root.currentStep.nextAction
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: continueButton
|
||||
text: root.currentStep.nextAction && root.currentStep.nextAction.text ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue")
|
||||
visible: root.currentStep.nextAction
|
||||
onClicked: root.currentStep.nextAction.trigger()
|
||||
icon.name: "arrow-right"
|
||||
enabled: root.currentStep.nextAction ? root.currentStep.nextAction.enabled : false
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Go back")
|
||||
visible: root.currentStep.previousAction
|
||||
onClicked: root.currentStep.previousAction.trigger()
|
||||
icon.name: "arrow-left"
|
||||
enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormDelegateSeparator {
|
||||
below: continueButton
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
id: continueButton
|
||||
text: root.currentStep.nextAction && root.currentStep.nextAction.text ? root.currentStep.nextAction.text : i18nc("@action:button", "Continue")
|
||||
visible: root.currentStep.nextAction
|
||||
onClicked: root.currentStep.nextAction.trigger()
|
||||
icon.name: "arrow-right"
|
||||
enabled: root.currentStep.nextAction ? root.currentStep.nextAction.enabled : false
|
||||
}
|
||||
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Go back")
|
||||
visible: root.currentStep.previousAction
|
||||
onClicked: root.currentStep.previousAction.trigger()
|
||||
icon.name: "arrow-left"
|
||||
enabled: root.currentStep.previousAction ? root.currentStep.previousAction.enabled : false
|
||||
}
|
||||
}
|
||||
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Open proxy settings")
|
||||
icon.name: "settings-configure"
|
||||
onClicked: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat.settings", "NetworkProxyPage"), {}, {
|
||||
title: i18nc("@title:window", "Proxy Settings")
|
||||
});
|
||||
FormCard.FormCard {
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing * 2
|
||||
maximumWidth: Kirigami.Units.gridUnit * 20
|
||||
FormCard.FormButtonDelegate {
|
||||
text: i18nc("@action:button", "Settings")
|
||||
icon.name: "settings-configure"
|
||||
onClicked: NeoChatSettingsView.open()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,7 +148,7 @@ int main(int argc, char *argv[])
|
||||
i18n("Maintainer"),
|
||||
QStringLiteral("carl@carlschwan.eu"),
|
||||
QStringLiteral("https://carlschwan.eu"),
|
||||
QStringLiteral("https://carlschwan.eu/avatar.png"));
|
||||
QUrl(QStringLiteral("https://carlschwan.eu/avatar.png")));
|
||||
about.addAuthor(i18n("Tobias Fella"), i18n("Maintainer"), QStringLiteral("tobias.fella@kde.org"), QStringLiteral("https://tobiasfella.de"));
|
||||
about.addAuthor(i18n("James Graham"), i18n("Maintainer"), QStringLiteral("james.h.graham@protonmail.com"));
|
||||
about.addCredit(i18n("Black Hat"), i18n("Original author of Spectral"), QStringLiteral("bhat@encom.eu.org"));
|
||||
@@ -185,9 +185,6 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
|
||||
ColorSchemer colorScheme;
|
||||
if (!NeoChatConfig::self()->colorScheme().isEmpty()) {
|
||||
colorScheme.apply(NeoChatConfig::self()->colorScheme());
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
|
||||
@@ -307,7 +304,6 @@ int main(int argc, char *argv[])
|
||||
QWindow *window = windowFromEngine(&engine);
|
||||
|
||||
WindowController::instance().setWindow(window);
|
||||
WindowController::instance().restoreGeometry();
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2019 Kitsune Ral <kitsune-ral@users.sf.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "matriximageprovider.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QStandardPaths>
|
||||
#include <QThread>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ThumbnailResponse::ThumbnailResponse(QString id, QSize size, NeoChatConnection *connection)
|
||||
: mediaId(std::move(id))
|
||||
, requestedSize(size)
|
||||
, localFile(QStringLiteral("%1/image_provider/%2-%3x%4.png")
|
||||
.arg(QStandardPaths::writableLocation(QStandardPaths::CacheLocation),
|
||||
mediaId,
|
||||
QString::number(requestedSize.width()),
|
||||
QString::number(requestedSize.height())))
|
||||
, m_connection(connection)
|
||||
, errorStr("Image request hasn't started"_ls)
|
||||
{
|
||||
if (requestedSize.isEmpty()) {
|
||||
requestedSize.setHeight(100);
|
||||
requestedSize.setWidth(100);
|
||||
}
|
||||
if (mediaId.count(QLatin1Char('/')) != 1) {
|
||||
if (mediaId.startsWith(QLatin1Char('/'))) {
|
||||
mediaId = mediaId.mid(1);
|
||||
} else {
|
||||
errorStr = i18n("Media id '%1' doesn't follow server/mediaId pattern", mediaId);
|
||||
Q_EMIT finished();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
mediaId = mediaId.split(QLatin1Char('?'))[0];
|
||||
|
||||
QImage cachedImage;
|
||||
if (cachedImage.load(localFile)) {
|
||||
image = cachedImage;
|
||||
errorStr.clear();
|
||||
Q_EMIT finished();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_connection) {
|
||||
qWarning() << "Current connection is null";
|
||||
return;
|
||||
}
|
||||
|
||||
// Execute a request on the main thread asynchronously
|
||||
moveToThread(m_connection->thread());
|
||||
QMetaObject::invokeMethod(this, &ThumbnailResponse::startRequest, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void ThumbnailResponse::startRequest()
|
||||
{
|
||||
if (!m_connection) {
|
||||
return;
|
||||
}
|
||||
// Runs in the main thread, not QML thread
|
||||
Q_ASSERT(QThread::currentThread() == m_connection->thread());
|
||||
job = m_connection->getThumbnail(mediaId, requestedSize);
|
||||
// Connect to any possible outcome including abandonment
|
||||
// to make sure the QML thread is not left stuck forever.
|
||||
connect(job, &BaseJob::finished, this, &ThumbnailResponse::prepareResult);
|
||||
}
|
||||
|
||||
void ThumbnailResponse::prepareResult()
|
||||
{
|
||||
Q_ASSERT(QThread::currentThread() == job->thread());
|
||||
Q_ASSERT(job->error() != BaseJob::Pending);
|
||||
{
|
||||
QWriteLocker _(&lock);
|
||||
if (job->error() == BaseJob::Success) {
|
||||
image = job->thumbnail();
|
||||
|
||||
QString localPath = QFileInfo(localFile).absolutePath();
|
||||
QDir dir;
|
||||
if (!dir.exists(localPath)) {
|
||||
dir.mkpath(localPath);
|
||||
}
|
||||
|
||||
image.save(localFile);
|
||||
|
||||
errorStr.clear();
|
||||
} else if (job->error() == BaseJob::Abandoned) {
|
||||
errorStr = i18n("Image request has been cancelled");
|
||||
// qDebug() << "ThumbnailResponse: cancelled for" << mediaId;
|
||||
} else {
|
||||
errorStr = job->errorString();
|
||||
qWarning() << "ThumbnailResponse: no valid image for" << mediaId << "-" << errorStr;
|
||||
}
|
||||
job = nullptr;
|
||||
}
|
||||
Q_EMIT finished();
|
||||
}
|
||||
|
||||
QQuickTextureFactory *ThumbnailResponse::textureFactory() const
|
||||
{
|
||||
QReadLocker _(&lock);
|
||||
return QQuickTextureFactory::textureFactoryForImage(image);
|
||||
}
|
||||
|
||||
QString ThumbnailResponse::errorString() const
|
||||
{
|
||||
QReadLocker _(&lock);
|
||||
return errorStr;
|
||||
}
|
||||
|
||||
QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||
{
|
||||
return new ThumbnailResponse(id, requestedSize, m_connection);
|
||||
}
|
||||
|
||||
#include "moc_matriximageprovider.cpp"
|
||||
@@ -1,80 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2019 Kitsune Ral <kitsune-ral@users.sf.net>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QQuickAsyncImageProvider>
|
||||
|
||||
#include <Quotient/jobs/mediathumbnailjob.h>
|
||||
|
||||
#include <QReadWriteLock>
|
||||
|
||||
class NeoChatConnection;
|
||||
|
||||
/**
|
||||
* @class ThumbnailResponse
|
||||
*
|
||||
* A QQuickImageResponse for an mxc image.
|
||||
*
|
||||
* @sa QQuickImageResponse
|
||||
*/
|
||||
class ThumbnailResponse : public QQuickImageResponse
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
explicit ThumbnailResponse(QString mediaId, QSize requestedSize, NeoChatConnection *m_connection);
|
||||
~ThumbnailResponse() override = default;
|
||||
|
||||
private Q_SLOTS:
|
||||
void startRequest();
|
||||
void prepareResult();
|
||||
|
||||
private:
|
||||
QString mediaId;
|
||||
QSize requestedSize;
|
||||
const QString localFile;
|
||||
Quotient::MediaThumbnailJob *job = nullptr;
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
|
||||
QImage image;
|
||||
QString errorStr;
|
||||
mutable QReadWriteLock lock; // Guards ONLY these two members above
|
||||
|
||||
QQuickTextureFactory *textureFactory() const override;
|
||||
QString errorString() const override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class MatrixImageProvider
|
||||
*
|
||||
* A QQuickAsyncImageProvider for mxc images.
|
||||
*
|
||||
* @sa QQuickAsyncImageProvider
|
||||
*/
|
||||
class MatrixImageProvider : public QQuickAsyncImageProvider
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection MEMBER m_connection)
|
||||
public:
|
||||
static MatrixImageProvider *create(QQmlEngine *engine, QJSEngine *)
|
||||
{
|
||||
static MatrixImageProvider *instance = new MatrixImageProvider;
|
||||
engine->setObjectOwnership(instance, QQmlEngine::CppOwnership);
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Return a job to provide the image with the given ID.
|
||||
*
|
||||
* @sa QQuickAsyncImageProvider::requestImageResponse
|
||||
*/
|
||||
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
|
||||
|
||||
private:
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
MatrixImageProvider() = default;
|
||||
};
|
||||
@@ -211,7 +211,7 @@ QList<ActionsModel::Action> actions{
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Positive, i18n("You are already in this room."));
|
||||
return QString();
|
||||
}
|
||||
if (room->members().contains(room->member(text))) {
|
||||
if (room->joinedMemberIds().contains(text)) {
|
||||
Q_EMIT Controller::instance().showMessage(Controller::Info, i18nc("<user> is already in this room.", "%1 is already in this room.", text));
|
||||
return QString();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "messagecontentmodel.h"
|
||||
#include "eventhandler.h"
|
||||
#include "neochatconfig.h"
|
||||
|
||||
#include <QImageReader>
|
||||
@@ -9,9 +10,10 @@
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Kirigami/Platform/PlatformTheme>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <KSyntaxHighlighting/Definition>
|
||||
@@ -19,10 +21,7 @@
|
||||
#endif
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "filetype.h"
|
||||
#include "itinerarymodel.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
@@ -30,20 +29,20 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply, bool isPending)
|
||||
: QAbstractListModel(nullptr)
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply, bool isPending, MessageContentModel *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_room(room)
|
||||
, m_eventId(event != nullptr ? event->id() : QString())
|
||||
, m_eventSenderId(event != nullptr ? event->senderId() : QString())
|
||||
, m_event(loadEvent<RoomEvent>(event->fullJson()))
|
||||
, m_isPending(isPending)
|
||||
, m_isReply(isReply)
|
||||
{
|
||||
intiializeEvent(event);
|
||||
initializeModel();
|
||||
}
|
||||
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply, bool isPending)
|
||||
: QAbstractListModel(nullptr)
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply, bool isPending, MessageContentModel *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_room(room)
|
||||
, m_eventId(eventId)
|
||||
, m_isPending(isPending)
|
||||
@@ -57,15 +56,27 @@ void MessageContentModel::initializeModel()
|
||||
Q_ASSERT(m_room != nullptr);
|
||||
// Allow making a model for an event that is being downloaded but will appear later
|
||||
// e.g. a reply, but we need an ID to know when it has arrived.
|
||||
Q_ASSERT(!m_eventId.isEmpty());
|
||||
// Also note that a pending event may not have an event ID yet but as long as we have an event
|
||||
// pointer we can pass out the transaction ID until it is set.
|
||||
Q_ASSERT(!m_eventId.isEmpty() || m_event != nullptr);
|
||||
|
||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
|
||||
if (m_room != nullptr) {
|
||||
if (eventId == m_eventId) {
|
||||
m_event = loadEvent<RoomEvent>(m_room->getEvent(eventId)->fullJson());
|
||||
Q_EMIT eventUpdated();
|
||||
m_notFound = false;
|
||||
intiializeEvent(m_room->getEvent(eventId));
|
||||
updateReplyModel();
|
||||
resetContent();
|
||||
resetModel();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
});
|
||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventNotFound, this, [this](const QString &eventId) {
|
||||
if (m_room != nullptr) {
|
||||
if (eventId == m_eventId) {
|
||||
m_notFound = true;
|
||||
resetModel();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -73,16 +84,18 @@ void MessageContentModel::initializeModel()
|
||||
});
|
||||
|
||||
if (m_event == nullptr) {
|
||||
m_room->downloadEventFromServer(m_eventId);
|
||||
intiializeEvent(m_room->getEvent(m_eventId));
|
||||
if (m_event == nullptr) {
|
||||
m_room->downloadEventFromServer(m_eventId);
|
||||
}
|
||||
}
|
||||
|
||||
connect(m_room, &NeoChatRoom::pendingEventAboutToMerge, this, [this](Quotient::RoomEvent *serverEvent) {
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_eventId == serverEvent->id()) {
|
||||
if (m_eventId == serverEvent->id() || m_eventId == serverEvent->transactionId()) {
|
||||
beginResetModel();
|
||||
m_isPending = false;
|
||||
m_event = loadEvent<RoomEvent>(serverEvent->fullJson());
|
||||
Q_EMIT eventUpdated();
|
||||
intiializeEvent(serverEvent);
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
@@ -91,8 +104,8 @@ void MessageContentModel::initializeModel()
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_eventId == newEvent->id()) {
|
||||
beginResetModel();
|
||||
m_event = loadEvent<RoomEvent>(newEvent->fullJson());
|
||||
Q_EMIT eventUpdated();
|
||||
intiializeEvent(newEvent);
|
||||
resetContent();
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
@@ -127,6 +140,13 @@ void MessageContentModel::initializeModel()
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) {
|
||||
if (m_event != nullptr && (oldThreadId == m_eventId || newThreadId == m_eventId)) {
|
||||
beginResetModel();
|
||||
resetContent(false, newThreadId == m_eventId);
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
|
||||
resetContent();
|
||||
});
|
||||
@@ -148,12 +168,47 @@ void MessageContentModel::initializeModel()
|
||||
}
|
||||
});
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ThreadsChanged, this, [this]() {
|
||||
updateReplyModel();
|
||||
resetModel();
|
||||
});
|
||||
|
||||
if (m_event != nullptr) {
|
||||
updateReplyModel();
|
||||
}
|
||||
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)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_event = loadEvent<RoomEvent>(event->fullJson());
|
||||
// a pending event may not previously have had an event ID so update.
|
||||
m_eventId = EventHandler::id(m_event.get());
|
||||
|
||||
auto senderId = m_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;
|
||||
@@ -166,7 +221,7 @@ void MessageContentModel::setShowAuthor(bool showAuthor)
|
||||
}
|
||||
m_showAuthor = showAuthor;
|
||||
|
||||
if (m_event != nullptr) {
|
||||
if (m_event != nullptr && m_room->connection()->isIgnored(m_event->senderId())) {
|
||||
if (showAuthor) {
|
||||
beginInsertRows({}, 0, 0);
|
||||
m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}});
|
||||
@@ -193,10 +248,23 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return {};
|
||||
}
|
||||
|
||||
EventHandler eventHandler(m_room, m_event.get());
|
||||
const auto component = m_components[index.row()];
|
||||
|
||||
if (role == DisplayRole) {
|
||||
if (m_notFound || (m_event && m_room->connection()->isIgnored(m_event->senderId()))) {
|
||||
Kirigami::Platform::PlatformTheme *theme =
|
||||
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
|
||||
QString disabledTextColor;
|
||||
if (theme != nullptr) {
|
||||
disabledTextColor = theme->disabledTextColor().name();
|
||||
} else {
|
||||
disabledTextColor = QStringLiteral("#000000");
|
||||
}
|
||||
return QString(QStringLiteral("<span style=\"color:%1\">").arg(disabledTextColor)
|
||||
+ i18nc("@info", "This message was either not found, you do not have permission to view it, or it was sent by an ignored user")
|
||||
+ QStringLiteral("</span>"));
|
||||
}
|
||||
if (component.type == MessageComponentType::Loading && m_isReply) {
|
||||
return i18n("Loading reply");
|
||||
}
|
||||
@@ -211,7 +279,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
if (!component.content.isEmpty()) {
|
||||
return component.content;
|
||||
}
|
||||
return eventHandler.getRichBody();
|
||||
return EventHandler::richBody(m_room, m_event.get());
|
||||
}
|
||||
if (role == ComponentTypeRole) {
|
||||
return component.type;
|
||||
@@ -220,7 +288,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return component.attributes;
|
||||
}
|
||||
if (role == EventIdRole) {
|
||||
return eventHandler.getId();
|
||||
return EventHandler::id(m_event.get());
|
||||
}
|
||||
if (role == TimeRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||
@@ -228,7 +296,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return eventHandler.getTime(m_isPending, lastUpdated);
|
||||
return EventHandler::time(m_event.get(), m_isPending, lastUpdated);
|
||||
}
|
||||
if (role == TimeStringRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||
@@ -236,13 +304,13 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return eventHandler.getTimeString(false, QLocale::ShortFormat, m_isPending, lastUpdated);
|
||||
return EventHandler::timeString(m_event.get(), QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||
}
|
||||
if (role == AuthorRole) {
|
||||
return QVariant::fromValue(eventHandler.getAuthor(m_isPending));
|
||||
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
|
||||
}
|
||||
if (role == MediaInfoRole) {
|
||||
return eventHandler.getMediaInfo();
|
||||
return EventHandler::mediaInfo(m_room, m_event.get());
|
||||
}
|
||||
if (role == FileTransferInfoRole) {
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(m_event.get()));
|
||||
@@ -251,25 +319,22 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
||||
}
|
||||
if (role == LatitudeRole) {
|
||||
return eventHandler.getLatitude();
|
||||
return EventHandler::latitude(m_event.get());
|
||||
}
|
||||
if (role == LongitudeRole) {
|
||||
return eventHandler.getLongitude();
|
||||
return EventHandler::longitude(m_event.get());
|
||||
}
|
||||
if (role == AssetRole) {
|
||||
return eventHandler.getLocationAssetType();
|
||||
return EventHandler::locationAssetType(m_event.get());
|
||||
}
|
||||
if (role == PollHandlerRole) {
|
||||
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
|
||||
}
|
||||
if (role == IsReplyRole) {
|
||||
return eventHandler.hasReply();
|
||||
}
|
||||
if (role == ReplyEventIdRole) {
|
||||
return eventHandler.getReplyId();
|
||||
return EventHandler::replyId(m_event.get());
|
||||
}
|
||||
if (role == ReplyAuthorRole) {
|
||||
return QVariant::fromValue(eventHandler.getReplyAuthor());
|
||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, m_event.get()));
|
||||
}
|
||||
if (role == ReplyContentModelRole) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
||||
@@ -282,6 +347,12 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
||||
}
|
||||
}
|
||||
if (role == ChatBarCacheRole) {
|
||||
if (m_room->threadCache()->threadId() == m_eventId) {
|
||||
return QVariant::fromValue<ChatBarCache *>(m_room->threadCache());
|
||||
}
|
||||
return QVariant::fromValue<ChatBarCache *>(m_room->editCache());
|
||||
}
|
||||
|
||||
return {};
|
||||
}
|
||||
@@ -309,11 +380,11 @@ QHash<int, QByteArray> MessageContentModel::roleNames() const
|
||||
roles[LongitudeRole] = "longitude";
|
||||
roles[AssetRole] = "asset";
|
||||
roles[PollHandlerRole] = "pollHandler";
|
||||
roles[IsReplyRole] = "isReply";
|
||||
roles[ReplyEventIdRole] = "replyEventId";
|
||||
roles[ReplyAuthorRole] = "replyAuthor";
|
||||
roles[ReplyContentModelRole] = "replyContentModel";
|
||||
roles[LinkPreviewerRole] = "linkPreviewer";
|
||||
roles[ChatBarCacheRole] = "chatBarCache";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -322,6 +393,12 @@ void MessageContentModel::resetModel()
|
||||
beginResetModel();
|
||||
m_components.clear();
|
||||
|
||||
if ((m_event && m_room->connection()->isIgnored(m_event->senderId())) || m_notFound) {
|
||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_event == nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
|
||||
endResetModel();
|
||||
@@ -336,7 +413,7 @@ void MessageContentModel::resetModel()
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void MessageContentModel::resetContent(bool isEditing)
|
||||
void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
{
|
||||
Q_ASSERT(m_event != nullptr);
|
||||
|
||||
@@ -345,7 +422,7 @@ void MessageContentModel::resetContent(bool isEditing)
|
||||
m_components.remove(startRow, rowCount() - startRow);
|
||||
endRemoveRows();
|
||||
|
||||
const auto newComponents = messageContentComponents(isEditing);
|
||||
const auto newComponents = messageContentComponents(isEditing, isThreading);
|
||||
if (newComponents.size() == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -354,7 +431,7 @@ void MessageContentModel::resetContent(bool isEditing)
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing)
|
||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
|
||||
{
|
||||
QList<MessageComponent> newComponents;
|
||||
|
||||
@@ -374,35 +451,45 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
newComponents += MessageComponent{MessageComponentType::Edit, QString(), {}};
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
} else {
|
||||
EventHandler eventHandler(m_room, m_event.get());
|
||||
newComponents.append(componentsForType(eventHandler.messageComponentType()));
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*m_event.get())));
|
||||
}
|
||||
|
||||
if (m_room->urlPreviewEnabled()) {
|
||||
newComponents = addLinkPreviews(newComponents);
|
||||
}
|
||||
|
||||
// If the event is already threaded the ThreadModel will handle displaying a chat bar.
|
||||
if (isThreading && !EventHandler::isThreaded(m_event.get())) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
}
|
||||
|
||||
return newComponents;
|
||||
}
|
||||
|
||||
void MessageContentModel::updateReplyModel()
|
||||
{
|
||||
if (m_event == nullptr || m_replyModel != nullptr || m_isReply) {
|
||||
if (m_event == nullptr || m_isReply) {
|
||||
return;
|
||||
}
|
||||
|
||||
EventHandler eventHandler(m_room, m_event.get());
|
||||
if (!eventHandler.hasReply()) {
|
||||
if (!EventHandler::hasReply(m_event.get()) || (EventHandler::isThreaded(m_event.get()) && NeoChatConfig::self()->threads())) {
|
||||
if (m_replyModel) {
|
||||
delete m_replyModel;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto replyEvent = m_room->findInTimeline(eventHandler.getReplyId());
|
||||
if (m_replyModel != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto replyEvent = m_room->findInTimeline(EventHandler::replyId(m_event.get()));
|
||||
if (replyEvent == m_room->historyEdge()) {
|
||||
m_replyModel = new MessageContentModel(m_room, eventHandler.getReplyId(), true);
|
||||
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(m_event.get()), true, false, this);
|
||||
} else {
|
||||
m_replyModel = new MessageContentModel(m_room, replyEvent->get(), true);
|
||||
m_replyModel = new MessageContentModel(m_room, replyEvent->get(), true, false, this);
|
||||
}
|
||||
|
||||
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
|
||||
|
||||
@@ -6,11 +6,12 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "itinerarymodel.h"
|
||||
#include "neochatroommember.h"
|
||||
|
||||
struct MessageComponent {
|
||||
MessageComponentType::Type type = MessageComponentType::Other;
|
||||
@@ -64,17 +65,21 @@ public:
|
||||
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||
PollHandlerRole, /**< The PollHandler for the event, if any. */
|
||||
|
||||
IsReplyRole, /**< Is the message a reply to another event. */
|
||||
ReplyEventIdRole, /**< The matrix ID of the message that was replied to. */
|
||||
ReplyAuthorRole, /**< The author of the event that was replied to. */
|
||||
ReplyContentModelRole, /**< The MessageContentModel for the reply event. */
|
||||
|
||||
LinkPreviewerRole, /**< The link preview details. */
|
||||
ChatBarCacheRole, /**< The ChatBarCache to use. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply = false, bool isPending = false);
|
||||
MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false, bool isPending = false);
|
||||
explicit MessageContentModel(NeoChatRoom *room,
|
||||
const Quotient::RoomEvent *event,
|
||||
bool isReply = false,
|
||||
bool isPending = false,
|
||||
MessageContentModel *parent = nullptr);
|
||||
MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false, bool isPending = false, MessageContentModel *parent = nullptr);
|
||||
|
||||
bool showAuthor() const;
|
||||
void setShowAuthor(bool showAuthor);
|
||||
@@ -115,18 +120,22 @@ private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
QString m_eventId;
|
||||
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_notFound = false;
|
||||
|
||||
void initializeModel();
|
||||
void intiializeEvent(const QString &eventId);
|
||||
void intiializeEvent(const Quotient::RoomEvent *event);
|
||||
|
||||
QList<MessageComponent> m_components;
|
||||
void resetModel();
|
||||
void resetContent(bool isEditing = false);
|
||||
QList<MessageComponent> messageContentComponents(bool isEditing = false);
|
||||
void resetContent(bool isEditing = false, bool isThreading = false);
|
||||
QList<MessageComponent> messageContentComponents(bool isEditing = false, bool isThreading = false);
|
||||
|
||||
QPointer<MessageContentModel> m_replyModel;
|
||||
void updateReplyModel();
|
||||
|
||||
@@ -22,11 +22,8 @@
|
||||
#include "enums/delegatetype.h"
|
||||
#include "eventhandler.h"
|
||||
#include "events/pollevent.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "messagecontentmodel.h"
|
||||
#include "models/messagefiltermodel.h"
|
||||
#include "models/reactionmodel.h"
|
||||
#include "readmarkermodel.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -69,6 +66,11 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
||||
connect(this, &MessageEventModel::modelReset, this, [this]() {
|
||||
resetting = false;
|
||||
});
|
||||
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::ThreadsChanged, this, [this]() {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
}
|
||||
|
||||
NeoChatRoom *MessageEventModel::room() const
|
||||
@@ -86,10 +88,16 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
// 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 = nullptr;
|
||||
endResetModel();
|
||||
|
||||
// Don't clear the member objects until the model has been fully reset and all
|
||||
// refs cleared.
|
||||
m_memberObjects.clear();
|
||||
m_contentModels.clear();
|
||||
m_reactionModels.clear();
|
||||
m_readMarkerModels.clear();
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
@@ -150,8 +158,9 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
refreshLastUserEvents(i);
|
||||
}
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] {
|
||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
||||
m_initialized = true;
|
||||
createEventObjects(event);
|
||||
beginInsertRows({}, 0, 0);
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
||||
@@ -214,22 +223,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
connect(m_currentRoom, &Room::memberNameUpdated, this, [this](RoomMember member) {
|
||||
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||
auto event = it->event();
|
||||
if (event->senderId() == member.id()) {
|
||||
refreshEventRoles(event->id(), {AuthorRole});
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(m_currentRoom, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
|
||||
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||
auto event = it->event();
|
||||
if (event->senderId() == member.id()) {
|
||||
refreshEventRoles(event->id(), {AuthorRole});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localMember().id();
|
||||
} else {
|
||||
@@ -397,6 +390,8 @@ void MessageEventModel::fetchMore(const QModelIndex &parent)
|
||||
}
|
||||
}
|
||||
|
||||
static NeochatRoomMember *emptyNeochatRoomMember = new NeochatRoomMember;
|
||||
|
||||
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if (!checkIndex(idx, QAbstractItemModel::CheckIndexOption::IndexIsValid)) {
|
||||
@@ -437,31 +432,30 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
const auto pendingIt = m_currentRoom->pendingEvents().crbegin() + std::min(row, timelineBaseIndex());
|
||||
const auto &evt = isPending ? **pendingIt : **timelineIt;
|
||||
|
||||
EventHandler eventHandler(m_currentRoom, &evt);
|
||||
|
||||
if (role == Qt::DisplayRole) {
|
||||
if (evt.isRedacted()) {
|
||||
auto reason = evt.redactedBecause()->reason();
|
||||
return (reason.isEmpty()) ? i18n("<i>[This message was deleted]</i>")
|
||||
: i18n("<i>[This message was deleted: %1]</i>", evt.redactedBecause()->reason());
|
||||
}
|
||||
return eventHandler.getRichBody();
|
||||
return EventHandler::richBody(m_currentRoom, &evt);
|
||||
}
|
||||
|
||||
if (role == ContentModelRole) {
|
||||
if (!evt.isStateEvent() && !evt.id().isEmpty()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_currentRoom, &evt));
|
||||
QString modelId;
|
||||
if (!evt.id().isEmpty() && m_contentModels.contains(evt.id())) {
|
||||
modelId = evt.id();
|
||||
} else if (!evt.transactionId().isEmpty() && m_contentModels.contains(evt.transactionId())) {
|
||||
modelId = evt.transactionId();
|
||||
}
|
||||
if (evt.isStateEvent()) {
|
||||
if (evt.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_currentRoom, &evt));
|
||||
}
|
||||
if (!modelId.isEmpty()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_contentModels.at(modelId).get());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == GenericDisplayRole) {
|
||||
return eventHandler.getGenericBody();
|
||||
return EventHandler::genericBody(&evt);
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
@@ -469,11 +463,22 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == AuthorRole) {
|
||||
return QVariant::fromValue(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) {
|
||||
return eventHandler.isHighlighted();
|
||||
return EventHandler::isHighlighted(m_currentRoom, &evt);
|
||||
}
|
||||
|
||||
if (role == SpecialMarksRole) {
|
||||
@@ -486,7 +491,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return pendingIt->deliveryStatus();
|
||||
}
|
||||
|
||||
if (eventHandler.isHidden()) {
|
||||
if (EventHandler::isHidden(m_currentRoom, &evt)) {
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
|
||||
if (EventHandler::isThreaded(&evt) && EventHandler::threadRoot(&evt) != EventHandler::id(&evt) && NeoChatConfig::threads()) {
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
|
||||
@@ -494,7 +503,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == EventIdRole) {
|
||||
return eventHandler.getId();
|
||||
return EventHandler::id(&evt);
|
||||
}
|
||||
|
||||
if (role == ProgressInfoRole) {
|
||||
@@ -510,20 +519,20 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (role == TimeRole) {
|
||||
auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
|
||||
return eventHandler.getTime(isPending, lastUpdated);
|
||||
return EventHandler::time(&evt, isPending, lastUpdated);
|
||||
}
|
||||
|
||||
if (role == SectionRole) {
|
||||
auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
|
||||
return eventHandler.getTimeString(true, QLocale::ShortFormat, isPending, lastUpdated);
|
||||
return EventHandler::timeString(&evt, true, QLocale::ShortFormat, isPending, lastUpdated);
|
||||
}
|
||||
|
||||
if (role == IsThreadedRole) {
|
||||
return eventHandler.isThreaded();
|
||||
return EventHandler::isThreaded(&evt);
|
||||
}
|
||||
|
||||
if (role == ThreadRootRole) {
|
||||
return eventHandler.threadRoot();
|
||||
return EventHandler::threadRoot(&evt);
|
||||
}
|
||||
|
||||
if (role == ShowSectionRole) {
|
||||
@@ -576,7 +585,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == AuthorDisplayNameRole) {
|
||||
return eventHandler.getAuthorDisplayName(isPending);
|
||||
return EventHandler::authorDisplayName(m_currentRoom, &evt, isPending);
|
||||
}
|
||||
|
||||
if (role == IsRedactedRole) {
|
||||
@@ -588,11 +597,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == MediaInfoRole) {
|
||||
return eventHandler.getMediaInfo();
|
||||
return EventHandler::mediaInfo(m_currentRoom, &evt);
|
||||
}
|
||||
|
||||
if (role == IsEditableRole) {
|
||||
return eventHandler.messageComponentType() == MessageComponentType::Text && evt.senderId() == m_currentRoom->localMember().id();
|
||||
return MessageComponentType::typeForEvent(evt) == MessageComponentType::Text && evt.senderId() == m_currentRoom->localMember().id();
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -619,6 +628,29 @@ void MessageEventModel::createEventObjects(const Quotient::RoomEvent *event)
|
||||
}
|
||||
|
||||
auto eventId = event->id();
|
||||
auto senderId = event->senderId();
|
||||
if (eventId.isEmpty()) {
|
||||
eventId = event->transactionId();
|
||||
}
|
||||
// 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));
|
||||
}
|
||||
|
||||
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
|
||||
if (!event->isStateEvent() || event->matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_currentRoom, event));
|
||||
}
|
||||
}
|
||||
|
||||
if (EventHandler::isThreaded(event) && !m_threadModels.contains(EventHandler::threadRoot(event))) {
|
||||
m_threadModels[EventHandler::threadRoot(event)] = QSharedPointer<ThreadModel>(new ThreadModel(EventHandler::threadRoot(event), m_currentRoom));
|
||||
}
|
||||
|
||||
// ReadMarkerModel handles updates to add and remove markers, we only need to
|
||||
// handle adding and removing whole models here.
|
||||
@@ -679,4 +711,9 @@ bool MessageEventModel::event(QEvent *event)
|
||||
return QObject::event(event);
|
||||
}
|
||||
|
||||
ThreadModel *MessageEventModel::threadModelForRootId(const QString &threadRootId) const
|
||||
{
|
||||
return m_threadModels[threadRootId].data();
|
||||
}
|
||||
|
||||
#include "moc_messageeventmodel.cpp"
|
||||
|
||||
@@ -8,9 +8,12 @@
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "linkpreviewer.h"
|
||||
#include "messagecontentmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatroommember.h"
|
||||
#include "pollhandler.h"
|
||||
#include "readmarkermodel.h"
|
||||
#include "threadmodel.h"
|
||||
|
||||
class ReactionModel;
|
||||
|
||||
@@ -53,8 +56,8 @@ public:
|
||||
|
||||
ContentModelRole, /**< The MessageContentModel for the event. */
|
||||
|
||||
IsThreadedRole,
|
||||
ThreadRootRole,
|
||||
IsThreadedRole, /**< Whether the message is in a thread. */
|
||||
ThreadRootRole, /**< The Matrix ID of the thread root message, if any . */
|
||||
|
||||
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||
|
||||
@@ -103,6 +106,8 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const;
|
||||
|
||||
Q_INVOKABLE ThreadModel *threadModelForRootId(const QString &threadRootId) const;
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
@@ -115,7 +120,10 @@ private:
|
||||
bool movingEvent = false;
|
||||
KFormat m_format;
|
||||
|
||||
std::map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
|
||||
std::map<QString, std::unique_ptr<MessageContentModel>> m_contentModels;
|
||||
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
|
||||
QMap<QString, QSharedPointer<ThreadModel>> m_threadModels;
|
||||
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
|
||||
|
||||
[[nodiscard]] int timelineBaseIndex() const;
|
||||
|
||||
@@ -4,12 +4,12 @@
|
||||
#include "messagefiltermodel.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <QVariant>
|
||||
|
||||
#include "enums/delegatetype.h"
|
||||
#include "messagecontentmodel.h"
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "timelinemodel.h"
|
||||
#include "neochatroommember.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
|
||||
@@ -121,12 +121,11 @@ void NotificationsModel::loadData()
|
||||
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == QStringLiteral("mxc") ? avatar : QUrl();
|
||||
|
||||
const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get());
|
||||
EventHandler eventHandler(dynamic_cast<NeoChatRoom *>(room), roomEvent);
|
||||
beginInsertRows({}, m_notifications.length(), m_notifications.length());
|
||||
m_notifications += Notification{
|
||||
.roomId = notification.roomId,
|
||||
.text = room->member(authorId).htmlSafeDisplayName() + (roomEvent->is<StateEvent>() ? QStringLiteral(" ") : QStringLiteral(": "))
|
||||
+ eventHandler.getPlainBody(true),
|
||||
+ EventHandler::plainBody(dynamic_cast<NeoChatRoom *>(room), roomEvent, true),
|
||||
.authorName = room->member(authorId).htmlSafeDisplayName(),
|
||||
.authorAvatar = authorAvatar,
|
||||
.eventId = roomEvent->id(),
|
||||
|
||||
@@ -2,20 +2,40 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "permissionsmodel.h"
|
||||
#include "powerlevel.h"
|
||||
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
|
||||
#include <KLazyLocalizedString>
|
||||
|
||||
#include "powerlevel.h"
|
||||
|
||||
using namespace Qt::Literals::StringLiterals;
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr auto UsersDefaultKey = "users_default"_L1;
|
||||
constexpr auto StateDefaultKey = "state_default"_L1;
|
||||
constexpr auto EventsDefaultKey = "events_default"_L1;
|
||||
|
||||
constexpr auto InviteKey = "invite"_L1;
|
||||
constexpr auto KickKey = "kick"_L1;
|
||||
constexpr auto BanKey = "ban"_L1;
|
||||
constexpr auto RedactKey = "redact"_L1;
|
||||
|
||||
static const QStringList defaultPermissions = {
|
||||
QStringLiteral("users_default"),
|
||||
QStringLiteral("state_default"),
|
||||
QStringLiteral("events_default"),
|
||||
QStringLiteral("invite"),
|
||||
QStringLiteral("kick"),
|
||||
QStringLiteral("ban"),
|
||||
QStringLiteral("redact"),
|
||||
UsersDefaultKey,
|
||||
StateDefaultKey,
|
||||
EventsDefaultKey,
|
||||
};
|
||||
|
||||
static const QStringList basicPermissions = {
|
||||
InviteKey,
|
||||
KickKey,
|
||||
BanKey,
|
||||
RedactKey,
|
||||
};
|
||||
|
||||
static const QStringList knownPermissions = {
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
QStringLiteral("m.room.power_levels"),
|
||||
@@ -33,14 +53,14 @@ static const QStringList defaultPermissions = {
|
||||
};
|
||||
|
||||
// Alternate name text for default permissions.
|
||||
static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
|
||||
{QStringLiteral("users_default"), kli18nc("Room permission type", "Default user power level")},
|
||||
{QStringLiteral("state_default"), kli18nc("Room permission type", "Default power level to set the room state")},
|
||||
{QStringLiteral("events_default"), kli18nc("Room permission type", "Default power level to send messages")},
|
||||
{QStringLiteral("invite"), kli18nc("Room permission type", "Invite users")},
|
||||
{QStringLiteral("kick"), kli18nc("Room permission type", "Kick users")},
|
||||
{QStringLiteral("ban"), kli18nc("Room permission type", "Ban users")},
|
||||
{QStringLiteral("redact"), kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||
static const QHash<QString, KLazyLocalizedString> permissionNames = {
|
||||
{UsersDefaultKey, kli18nc("Room permission type", "Default user power level")},
|
||||
{StateDefaultKey, kli18nc("Room permission type", "Default power level to set the room state")},
|
||||
{EventsDefaultKey, kli18nc("Room permission type", "Default power level to send messages")},
|
||||
{InviteKey, kli18nc("Room permission type", "Invite users")},
|
||||
{KickKey, kli18nc("Room permission type", "Kick users")},
|
||||
{BanKey, kli18nc("Room permission type", "Ban users")},
|
||||
{RedactKey, kli18nc("Room permission type", "Remove messages sent by other users")},
|
||||
{QStringLiteral("m.reaction"), kli18nc("Room permission type", "Send reactions")},
|
||||
{QStringLiteral("m.room.redaction"), kli18nc("Room permission type", "Remove their own messages")},
|
||||
{QStringLiteral("m.room.power_levels"), kli18nc("Room permission type", "Change user permissions")},
|
||||
@@ -58,10 +78,10 @@ static const QHash<QString, KLazyLocalizedString> defaultPermissionNames = {
|
||||
};
|
||||
|
||||
// Subtitles for the default values.
|
||||
static const QHash<QString, KLazyLocalizedString> defaultSubtitles = {
|
||||
{QStringLiteral("users_default"), kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||
{QStringLiteral("state_default"), kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||
{QStringLiteral("events_default"), kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||
static const QHash<QString, KLazyLocalizedString> permissionSubtitles = {
|
||||
{UsersDefaultKey, kli18nc("Room permission type", "This is the power level for all new users when joining the room")},
|
||||
{StateDefaultKey, kli18nc("Room permission type", "This is used for all state events that do not have their own entry here")},
|
||||
{EventsDefaultKey, kli18nc("Room permission type", "This is used for all message events that do not have their own entry here")},
|
||||
};
|
||||
|
||||
// Permissions that should use the event default.
|
||||
@@ -70,6 +90,7 @@ static const QStringList eventPermissions = {
|
||||
QStringLiteral("m.reaction"),
|
||||
QStringLiteral("m.room.redaction"),
|
||||
};
|
||||
};
|
||||
|
||||
PermissionsModel::PermissionsModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
@@ -109,6 +130,8 @@ void PermissionsModel::initializeModel()
|
||||
}
|
||||
|
||||
m_permissions.append(defaultPermissions);
|
||||
m_permissions.append(basicPermissions);
|
||||
m_permissions.append(knownPermissions);
|
||||
|
||||
for (const auto &event : currentPowerLevelEvent->events().keys()) {
|
||||
if (!m_permissions.contains(event)) {
|
||||
@@ -131,17 +154,17 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
const auto permission = m_permissions.value(index.row());
|
||||
if (role == NameRole) {
|
||||
if (defaultPermissionNames.keys().contains(permission)) {
|
||||
return defaultPermissionNames.value(permission).toString();
|
||||
if (permissionNames.keys().contains(permission)) {
|
||||
return permissionNames.value(permission).toString();
|
||||
}
|
||||
return permission;
|
||||
}
|
||||
if (role == SubtitleRole) {
|
||||
if (permission.startsWith(QLatin1String("m.")) && defaultPermissionNames.keys().contains(permission)) {
|
||||
if (knownPermissions.contains(permission) && permissionNames.keys().contains(permission)) {
|
||||
return permission;
|
||||
}
|
||||
if (defaultSubtitles.contains(permission)) {
|
||||
return defaultSubtitles.value(permission).toString();
|
||||
if (permissionSubtitles.contains(permission)) {
|
||||
return permissionSubtitles.value(permission).toString();
|
||||
}
|
||||
return QString();
|
||||
}
|
||||
@@ -166,11 +189,10 @@ QVariant PermissionsModel::data(const QModelIndex &index, int role) const
|
||||
return QString();
|
||||
}
|
||||
if (role == IsDefaultValueRole) {
|
||||
return permission.contains(QLatin1String("default"));
|
||||
return defaultPermissions.contains(permission);
|
||||
}
|
||||
if (role == IsBasicPermissionRole) {
|
||||
return permission == QStringLiteral("invite") || permission == QStringLiteral("kick") || permission == QStringLiteral("ban")
|
||||
|| permission == QStringLiteral("redact");
|
||||
return basicPermissions.contains(permission);
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -201,19 +223,19 @@ std::optional<int> PermissionsModel::powerLevel(const QString &permission) const
|
||||
}
|
||||
|
||||
if (const auto currentPowerLevelEvent = m_room->currentState().get<Quotient::RoomPowerLevelsEvent>()) {
|
||||
if (permission == QStringLiteral("ban")) {
|
||||
if (permission == BanKey) {
|
||||
return currentPowerLevelEvent->ban();
|
||||
} else if (permission == QStringLiteral("kick")) {
|
||||
} else if (permission == KickKey) {
|
||||
return currentPowerLevelEvent->kick();
|
||||
} else if (permission == QStringLiteral("invite")) {
|
||||
} else if (permission == InviteKey) {
|
||||
return currentPowerLevelEvent->invite();
|
||||
} else if (permission == QStringLiteral("redact")) {
|
||||
} else if (permission == RedactKey) {
|
||||
return currentPowerLevelEvent->redact();
|
||||
} else if (permission == QStringLiteral("users_default")) {
|
||||
} else if (permission == UsersDefaultKey) {
|
||||
return currentPowerLevelEvent->usersDefault();
|
||||
} else if (permission == QStringLiteral("state_default")) {
|
||||
} else if (permission == StateDefaultKey) {
|
||||
return currentPowerLevelEvent->stateDefault();
|
||||
} else if (permission == QStringLiteral("events_default")) {
|
||||
} else if (permission == EventsDefaultKey) {
|
||||
return currentPowerLevelEvent->eventsDefault();
|
||||
} else if (eventPermissions.contains(permission)) {
|
||||
return currentPowerLevelEvent->powerLevelForEvent(permission);
|
||||
@@ -241,6 +263,9 @@ void PermissionsModel::setPowerLevel(const QString &permission, const int &newPo
|
||||
auto powerLevelContent = currentPowerLevelEvent->contentJson();
|
||||
if (powerLevelContent.contains(permission)) {
|
||||
powerLevelContent[permission] = clampPowerLevel;
|
||||
// Deal with the case where a default or basic permission is missing from the event content erroneously.
|
||||
} else if (defaultPermissions.contains(permission) || basicPermissions.contains(permission)) {
|
||||
powerLevelContent[permission] = clampPowerLevel;
|
||||
} else {
|
||||
auto eventPowerLevels = powerLevelContent[QLatin1String("events")].toObject();
|
||||
eventPowerLevels[permission] = clampPowerLevel;
|
||||
|
||||
@@ -11,7 +11,6 @@
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
|
||||
#include <KLazyLocalizedString>
|
||||
|
||||
|
||||
@@ -2,21 +2,13 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "reactionmodel.h"
|
||||
#include "neochatroom.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFont>
|
||||
#ifdef HAVE_ICU
|
||||
#include <QTextBoundaryFinder>
|
||||
#include <QTextCharFormat>
|
||||
#include <unicode/uchar.h>
|
||||
#include <unicode/urename.h>
|
||||
#endif
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
|
||||
: QAbstractListModel(nullptr)
|
||||
, m_room(room)
|
||||
@@ -157,30 +149,6 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
bool isEmoji(const QString &text)
|
||||
{
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
int from = 0;
|
||||
while (finder.toNextBoundary() != -1) {
|
||||
auto to = finder.position();
|
||||
if (text[from].isSpace()) {
|
||||
from = to;
|
||||
continue;
|
||||
}
|
||||
|
||||
auto first = text.mid(from, to - from).toUcs4()[0];
|
||||
if (!u_hasBinaryProperty(first, UCHAR_EMOJI)) {
|
||||
return false;
|
||||
}
|
||||
from = to;
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
QString ReactionModel::reactionText(QString text) const
|
||||
{
|
||||
text = text.toHtmlEscaped();
|
||||
@@ -194,7 +162,7 @@ QString ReactionModel::reactionText(QString text) const
|
||||
.arg(m_room->connection()->makeMediaUrl(QUrl(text)).toString(), QString::number(size));
|
||||
}
|
||||
|
||||
return isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
return Utils::isEmoji(text) ? QStringLiteral("<span style=\"font-family: 'emoji';\">") + text + QStringLiteral("</span>") : text;
|
||||
}
|
||||
|
||||
#include "moc_reactionmodel.cpp"
|
||||
|
||||
@@ -245,8 +245,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
|
||||
return QString();
|
||||
}
|
||||
EventHandler eventHandler(room, room->lastEvent());
|
||||
return eventHandler.subtitleText();
|
||||
return EventHandler::subtitleText(room, room->lastEvent());
|
||||
}
|
||||
if (role == AvatarImageRole) {
|
||||
return room->avatar(128);
|
||||
|
||||
@@ -357,8 +357,7 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
|
||||
return QString();
|
||||
}
|
||||
EventHandler eventHandler(room, room->lastEvent());
|
||||
return eventHandler.subtitleText();
|
||||
return EventHandler::subtitleText(room, room->lastEvent());
|
||||
}
|
||||
if (role == AvatarImageRole) {
|
||||
return room->avatar(128);
|
||||
|
||||
@@ -66,7 +66,17 @@ void SearchModel::search()
|
||||
m_job = job;
|
||||
connect(job, &BaseJob::finished, this, [this, job] {
|
||||
beginResetModel();
|
||||
m_memberObjects.clear();
|
||||
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();
|
||||
setSearching(false);
|
||||
m_job = nullptr;
|
||||
@@ -79,18 +89,16 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
auto row = index.row();
|
||||
const auto &event = *m_result->results[row].result;
|
||||
|
||||
EventHandler eventHandler(m_room, &event);
|
||||
|
||||
switch (role) {
|
||||
case AuthorRole:
|
||||
return QVariant::fromValue(eventHandler.getAuthor());
|
||||
return QVariant::fromValue<NeochatRoomMember *>(m_memberObjects.at(event.senderId()).get());
|
||||
case ShowSectionRole:
|
||||
if (row == 0) {
|
||||
return true;
|
||||
}
|
||||
return event.originTimestamp().date() != m_result->results[row - 1].result->originTimestamp().date();
|
||||
case SectionRole:
|
||||
return eventHandler.getTimeString(true);
|
||||
return EventHandler::timeString(&event, true);
|
||||
case ShowReactionsRole:
|
||||
return false;
|
||||
case ShowReadMarkersRole:
|
||||
@@ -98,13 +106,13 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
case IsPendingRole:
|
||||
return false;
|
||||
case HighlightRole:
|
||||
return eventHandler.isHighlighted();
|
||||
return EventHandler::isHighlighted(m_room, &event);
|
||||
case EventIdRole:
|
||||
return eventHandler.getId();
|
||||
return EventHandler::id(&event);
|
||||
case IsThreadedRole:
|
||||
return eventHandler.isThreaded();
|
||||
return EventHandler::isThreaded(&event);
|
||||
case ThreadRootRole:
|
||||
return eventHandler.threadRoot();
|
||||
return EventHandler::threadRoot(&event);
|
||||
case ContentModelRole: {
|
||||
if (!event.isStateEvent()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, &event));
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <Quotient/csapi/search.h>
|
||||
|
||||
#include "neochatroommember.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
@@ -123,4 +125,6 @@ private:
|
||||
std::optional<Quotient::SearchJob::ResultRoomEvents> m_result = std::nullopt;
|
||||
Quotient::SearchJob *m_job = nullptr;
|
||||
bool m_searching = false;
|
||||
|
||||
std::map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
|
||||
190
src/models/threadmodel.cpp
Normal file
190
src/models/threadmodel.cpp
Normal file
@@ -0,0 +1,190 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "threadmodel.h"
|
||||
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/omittable.h>
|
||||
#include <memory>
|
||||
|
||||
#include "chatbarcache.h"
|
||||
#include "eventhandler.h"
|
||||
#include "messagecomponenttype.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
|
||||
: QConcatenateTablesProxyModel(room)
|
||||
, m_threadRootId(threadRootId)
|
||||
, m_threadChatBarModel(new ThreadChatBarModel(this, room))
|
||||
{
|
||||
Q_ASSERT(!m_threadRootId.isEmpty());
|
||||
Q_ASSERT(room);
|
||||
|
||||
m_threadRootContentModel = std::unique_ptr<MessageContentModel>(new MessageContentModel(room, threadRootId));
|
||||
|
||||
connect(room, &Quotient::Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
||||
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
|
||||
addNewEvent(event);
|
||||
addModels();
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(room, &Quotient::Room::aboutToAddNewMessages, this, [this](Quotient::RoomEventsRange events) {
|
||||
for (const auto &event : events) {
|
||||
if (auto roomEvent = eventCast<const Quotient::RoomMessageEvent>(event)) {
|
||||
if (EventHandler::isThreaded(roomEvent) && EventHandler::threadRoot(roomEvent) == m_threadRootId) {
|
||||
addNewEvent(roomEvent);
|
||||
}
|
||||
}
|
||||
}
|
||||
addModels();
|
||||
});
|
||||
|
||||
fetchMore({});
|
||||
addModels();
|
||||
}
|
||||
|
||||
QString ThreadModel::threadRootId() const
|
||||
{
|
||||
return m_threadRootId;
|
||||
}
|
||||
|
||||
MessageContentModel *ThreadModel::threadRootContentModel() const
|
||||
{
|
||||
return m_threadRootContentModel.get();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ThreadModel::roleNames() const
|
||||
{
|
||||
return m_threadRootContentModel->roleNames();
|
||||
}
|
||||
|
||||
bool ThreadModel::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return !m_currentJob && m_nextBatch.has_value();
|
||||
}
|
||||
|
||||
void ThreadModel::fetchMore(const QModelIndex &parent)
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
if (!m_currentJob && m_nextBatch.has_value()) {
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
const auto connection = room->connection();
|
||||
m_currentJob =
|
||||
connection->callApi<Quotient::GetRelatingEventsWithRelTypeJob>(room->id(), m_threadRootId, QLatin1String("m.thread"), *m_nextBatch, QString(), 5);
|
||||
connect(m_currentJob, &Quotient::BaseJob::success, this, [this]() {
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
auto newEvents = m_currentJob->chunk();
|
||||
for (auto &event : newEvents) {
|
||||
m_contentModels.push_back(new MessageContentModel(room, event.get()));
|
||||
}
|
||||
|
||||
addModels();
|
||||
|
||||
const auto newNextBatch = m_currentJob->nextBatch();
|
||||
if (!newNextBatch.isEmpty() && *m_nextBatch != newNextBatch) {
|
||||
*m_nextBatch = newNextBatch;
|
||||
} else {
|
||||
// Insert the thread root at the end.
|
||||
beginInsertRows({}, rowCount(), rowCount());
|
||||
endInsertRows();
|
||||
m_nextBatch.reset();
|
||||
}
|
||||
|
||||
m_currentJob.clear();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
void ThreadModel::addNewEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
|
||||
m_contentModels.push_front(new MessageContentModel(room, event));
|
||||
}
|
||||
|
||||
void ThreadModel::addModels()
|
||||
{
|
||||
if (!sourceModels().isEmpty()) {
|
||||
clearModels();
|
||||
}
|
||||
|
||||
addSourceModel(m_threadRootContentModel.get());
|
||||
for (auto it = m_contentModels.crbegin(); it != m_contentModels.crend(); ++it) {
|
||||
addSourceModel(*it);
|
||||
}
|
||||
addSourceModel(m_threadChatBarModel);
|
||||
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void ThreadModel::clearModels()
|
||||
{
|
||||
removeSourceModel(m_threadRootContentModel.get());
|
||||
for (const auto &model : m_contentModels) {
|
||||
if (sourceModels().contains(model)) {
|
||||
removeSourceModel(model);
|
||||
}
|
||||
}
|
||||
removeSourceModel(m_threadChatBarModel);
|
||||
}
|
||||
|
||||
ThreadChatBarModel::ThreadChatBarModel(QObject *parent, NeoChatRoom *room)
|
||||
: QAbstractListModel(parent)
|
||||
, m_room(room)
|
||||
{
|
||||
if (m_room != nullptr) {
|
||||
connect(m_room->threadCache(), &ChatBarCache::threadIdChanged, this, [this](const QString &oldThreadId, const QString &newThreadId) {
|
||||
const auto threadModel = dynamic_cast<ThreadModel *>(this->parent());
|
||||
if (threadModel != nullptr && (oldThreadId == threadModel->threadRootId() || newThreadId == threadModel->threadRootId())) {
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QVariant ThreadChatBarModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
if (idx.row() > 1) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == ComponentTypeRole) {
|
||||
return MessageComponentType::ChatBar;
|
||||
}
|
||||
if (role == ChatBarCacheRole) {
|
||||
if (m_room == nullptr) {
|
||||
return {};
|
||||
}
|
||||
return QVariant::fromValue<ChatBarCache *>(m_room->threadCache());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int ThreadChatBarModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
if (m_room == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
const auto threadModel = dynamic_cast<ThreadModel *>(this->parent());
|
||||
if (threadModel != nullptr) {
|
||||
return m_room->threadCache()->threadId() == threadModel->threadRootId() ? 1 : 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ThreadChatBarModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{ComponentTypeRole, "componentType"},
|
||||
{ChatBarCacheRole, "chatBarCache"},
|
||||
};
|
||||
}
|
||||
|
||||
#include "moc_threadmodel.cpp"
|
||||
141
src/models/threadmodel.h
Normal file
141
src/models/threadmodel.h
Normal file
@@ -0,0 +1,141 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <Quotient/util.h>
|
||||
|
||||
#include <QConcatenateTablesProxyModel>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <QPointer>
|
||||
#include <Quotient/csapi/relations.h>
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
|
||||
#include "linkpreviewer.h"
|
||||
#include "messagecontentmodel.h"
|
||||
|
||||
class NeoChatRoom;
|
||||
class ReactionModel;
|
||||
|
||||
/**
|
||||
* @class ThreadChatBarModel
|
||||
*
|
||||
* A model to provide a chat bar component to send new messages in a thread.
|
||||
*/
|
||||
class ThreadChatBarModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*
|
||||
* The role values need to match MessageContentModel not to blow up.
|
||||
*
|
||||
* @sa MessageContentModel
|
||||
*/
|
||||
enum Roles {
|
||||
ComponentTypeRole = MessageContentModel::ComponentTypeRole, /**< The type of component to visualise the message. */
|
||||
ChatBarCacheRole = MessageContentModel::ChatBarCacheRole, /**< The ChatBarCache to use. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit ThreadChatBarModel(QObject *parent, NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief 1 or 0, depending on whether a chat bar should be shown.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a map with ComponentTypeRole it's the only one.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ThreadModel
|
||||
*
|
||||
* This class defines the model for visualising a thread.
|
||||
*
|
||||
* The class also provides functions to access the data of the root event, typically
|
||||
* used to visualise the thread in a list of room threads.
|
||||
*/
|
||||
class ThreadModel : public QConcatenateTablesProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
public:
|
||||
explicit ThreadModel(const QString &threadRootId, NeoChatRoom *room);
|
||||
|
||||
QString threadRootId() const;
|
||||
|
||||
/**
|
||||
* @brief The content model for the thread root event.
|
||||
*/
|
||||
MessageContentModel *threadRootContentModel() const;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Whether there is more data available for the model to fetch.
|
||||
*
|
||||
* @sa QAbstractItemModel::canFetchMore()
|
||||
*/
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
|
||||
/**
|
||||
* @brief Fetches the next batch of model data if any is available.
|
||||
*
|
||||
* @sa QAbstractItemModel::fetchMore()
|
||||
*/
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
|
||||
private:
|
||||
QString m_threadRootId;
|
||||
|
||||
std::unique_ptr<MessageContentModel> m_threadRootContentModel;
|
||||
|
||||
std::deque<MessageContentModel *> m_contentModels;
|
||||
ThreadChatBarModel *m_threadChatBarModel;
|
||||
|
||||
QList<QString> m_events;
|
||||
QList<QString> m_pendingEvents;
|
||||
|
||||
std::unordered_map<QString, std::unique_ptr<Quotient::RoomEvent>> m_unloadedEvents;
|
||||
|
||||
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
|
||||
|
||||
QPointer<Quotient::GetRelatingEventsWithRelTypeJob> m_currentJob = nullptr;
|
||||
std::optional<QString> m_nextBatch = QString();
|
||||
bool m_addingPending = false;
|
||||
|
||||
void addNewEvent(const Quotient::RoomEvent *event);
|
||||
void addModels();
|
||||
void clearModels();
|
||||
};
|
||||
@@ -3,11 +3,15 @@
|
||||
|
||||
#include "timelinemodel.h"
|
||||
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
|
||||
#include "delegatetype.h"
|
||||
|
||||
TimelineModel::TimelineModel(QObject *parent)
|
||||
: QConcatenateTablesProxyModel(parent)
|
||||
{
|
||||
m_timelineBeginningModel = new TimelineBeginningModel(this);
|
||||
addSourceModel(m_timelineBeginningModel);
|
||||
m_messageEventModel = new MessageEventModel(this);
|
||||
addSourceModel(m_messageEventModel);
|
||||
m_timelineEndModel = new TimelineEndModel(this);
|
||||
@@ -23,6 +27,7 @@ void TimelineModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
// Both models do their own null checking so just pass along.
|
||||
m_messageEventModel->setRoom(room);
|
||||
m_timelineBeginningModel->setRoom(room);
|
||||
m_timelineEndModel->setRoom(room);
|
||||
}
|
||||
|
||||
@@ -36,6 +41,69 @@ QHash<int, QByteArray> TimelineModel::roleNames() const
|
||||
return m_messageEventModel->roleNames();
|
||||
}
|
||||
|
||||
TimelineBeginningModel::TimelineBeginningModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void TimelineBeginningModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (room == m_room) {
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
|
||||
if (m_room != nullptr) {
|
||||
m_room->disconnect(this);
|
||||
}
|
||||
|
||||
m_room = room;
|
||||
|
||||
if (m_room != nullptr) {
|
||||
Quotient::connectUntil(m_room.get(), &Quotient::Room::eventsHistoryJobChanged, this, [this]() {
|
||||
if (m_room && m_room->allHistoryLoaded()) {
|
||||
// HACK: We have to do it this way because DelegateChooser doesn't update dynamically.
|
||||
beginRemoveRows({}, 0, 0);
|
||||
endRemoveRows();
|
||||
beginInsertRows({}, 0, 0);
|
||||
endInsertRows();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
QVariant TimelineBeginningModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
Q_UNUSED(idx)
|
||||
if (m_room == nullptr) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
return DelegateType::Successor;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int TimelineBeginningModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
if (m_room == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return m_room->successorId().isEmpty() ? 0 : 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> TimelineBeginningModel::roleNames() const
|
||||
{
|
||||
return {{DelegateTypeRole, "delegateType"}};
|
||||
}
|
||||
|
||||
TimelineEndModel::TimelineEndModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
@@ -78,7 +146,11 @@ QVariant TimelineEndModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
return m_room->allHistoryLoaded() ? DelegateType::TimelineEnd : DelegateType::Loading;
|
||||
if (idx.row() == 1 || rowCount() == 1) {
|
||||
return m_room->allHistoryLoaded() ? DelegateType::TimelineEnd : DelegateType::Loading;
|
||||
} else {
|
||||
return DelegateType::Predecessor;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -86,7 +158,10 @@ QVariant TimelineEndModel::data(const QModelIndex &idx, int role) const
|
||||
int TimelineEndModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
return 1;
|
||||
if (m_room == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return m_room->predecessorId().isEmpty() ? 1 : (m_room->allHistoryLoaded() ? 2 : 1);
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> TimelineEndModel::roleNames() const
|
||||
|
||||
@@ -10,6 +10,57 @@
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
/**
|
||||
* @class TimelineBeginningModel
|
||||
*
|
||||
* A model to provide a delegate at the start of the timeline to show upgrades.
|
||||
*/
|
||||
class TimelineBeginningModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
DelegateTypeRole = MessageEventModel::DelegateTypeRole, /**< The delegate type of the message. */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
explicit TimelineBeginningModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Set the room for the timeline.
|
||||
*/
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief 1, the answer is always 1.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a map with DelegateTypeRole it's the only one.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room = nullptr;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class TimelineEndModel
|
||||
*
|
||||
@@ -108,5 +159,6 @@ Q_SIGNALS:
|
||||
|
||||
private:
|
||||
MessageEventModel *m_messageEventModel = nullptr;
|
||||
TimelineBeginningModel *m_timelineBeginningModel = nullptr;
|
||||
TimelineEndModel *m_timelineEndModel = nullptr;
|
||||
};
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <Quotient/avatar.h>
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "enums/powerlevel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
@@ -125,7 +125,7 @@ void WebShortcutModel::trigger(const QString &data)
|
||||
void WebShortcutModel::configureWebShortcuts()
|
||||
{
|
||||
#ifdef HAVE_KIO
|
||||
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts"), this);
|
||||
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell6"), QStringList() << QStringLiteral("webshortcuts"), this);
|
||||
job->exec();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ Name[eu]=NeoChat
|
||||
Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[gl]=NeoChat
|
||||
Name[he]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
@@ -58,6 +59,7 @@ Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat
|
||||
Comment[fi]=Hajautetun Matrix-viestintäyhteyskäytännön asiakasohjelma
|
||||
Comment[fr]=Un client pour « Matrix », le protocole décentralisé de communications.
|
||||
Comment[gl]=Un cliente para Matrix, o protocolo de comunicación descentralizada.
|
||||
Comment[he]=לקוח ל־matrix, פרוטוקול התקשורת המבוזר
|
||||
Comment[hu]=Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz
|
||||
Comment[ia]=Un cliente per Matrix, le protocollo de communication decentralisate
|
||||
Comment[id]=Sebuah klien untuk matrix, protokol komunikasi terdecentralisasi
|
||||
@@ -102,6 +104,7 @@ Name[eu]=Mezu berria
|
||||
Name[fi]=Uusi viesti
|
||||
Name[fr]=Nouveau message
|
||||
Name[gl]=Nova mensaxe
|
||||
Name[he]=הודעה חדשה
|
||||
Name[hu]=Új üzenet
|
||||
Name[ia]=Nove message
|
||||
Name[id]=Pesan baru
|
||||
@@ -142,6 +145,7 @@ Comment[eu]=Mezu berri bat dago
|
||||
Comment[fi]=Saapui uusi viesti
|
||||
Comment[fr]=Il y a un nouveau message
|
||||
Comment[gl]=Hai unha nova mensaxe.
|
||||
Comment[he]=יש הודעה חדשה
|
||||
Comment[hu]=Új üzenet érkezett
|
||||
Comment[ia]=Il ha un nove message
|
||||
Comment[id]=Ada pesan baru
|
||||
@@ -186,6 +190,7 @@ Name[eu]=Gonbidapen berria
|
||||
Name[fi]=Uusi kutsu
|
||||
Name[fr]=Nouvelle invitation
|
||||
Name[gl]=Novo convite
|
||||
Name[he]=הזמנה חדשה
|
||||
Name[hu]=Új meghívó
|
||||
Name[ia]=Nove invitation
|
||||
Name[id]=Undangan Baru
|
||||
@@ -225,6 +230,7 @@ Comment[eu]=Gela baterako gonbidapen berri bat dago
|
||||
Comment[fi]=Uusi kutsu huoneeseen
|
||||
Comment[fr]=Il y a une nouvelle invitation dans un salon.
|
||||
Comment[gl]=Tes un novo convite para unha sala.
|
||||
Comment[he]=יש הזמנה חדשה לחדר
|
||||
Comment[hu]=Új meghívó érkezett egy szobába
|
||||
Comment[ia]=Il ha un nove invitation a un sala
|
||||
Comment[id]=Ada undangan baru ke sebuah ruangan
|
||||
@@ -263,6 +269,8 @@ Name[es]=Compartir
|
||||
Name[eu]=Partekatu
|
||||
Name[fi]=Jaa
|
||||
Name[fr]=Partager
|
||||
Name[gl]=Compartir
|
||||
Name[he]=שיתוף
|
||||
Name[hu]=Megosztás
|
||||
Name[ia]=Comparti
|
||||
Name[it]=Condivisione
|
||||
@@ -271,6 +279,7 @@ Name[lv]=Kopīgot
|
||||
Name[nl]=Gedeelde
|
||||
Name[nn]=Del
|
||||
Name[pl]=Udostępnij
|
||||
Name[pt_BR]=Compartilhar
|
||||
Name[sl]=Deli
|
||||
Name[sv]=Dela
|
||||
Name[ta]=பகிர்
|
||||
@@ -288,6 +297,8 @@ Comment[es]=El resultado de compartir una parte de contenido
|
||||
Comment[eu]=Eduki pieza bat partekatzearen emaitza
|
||||
Comment[fi]=Tulos yhden sisältöosasen jakamisesta
|
||||
Comment[fr]=Le résultat du partage d'une partie de contenu.
|
||||
Comment[gl]=O resultado de compartir un fragmento de contido.
|
||||
Comment[he]=תוצאת שיתוף פיסת תוכן
|
||||
Comment[hu]=Tartalom megosztásának eredménye
|
||||
Comment[ia]=Le exito de compartir un pecietta de contento
|
||||
Comment[it]=Il risultato della condivisione di un contenuto
|
||||
@@ -296,6 +307,7 @@ Comment[lv]=Satura kopīgošanas rezultāts
|
||||
Comment[nl]=Het resultaat van het delen van een stukje inhoud
|
||||
Comment[nn]=Resultatet av deling av innhald
|
||||
Comment[pl]=Wynik udostępniania kawałka treści
|
||||
Comment[pt_BR]=O resultado de compartilhar um conteúdo
|
||||
Comment[sl]=Rezultat deljenega kosa vsebine
|
||||
Comment[sv]=Resultatet av att dela innehåll
|
||||
Comment[ta]=எதையோ பகிர்ந்ததன் விளைவு
|
||||
|
||||
@@ -15,9 +15,6 @@
|
||||
<entry name="OpenRoom" type="String">
|
||||
<label>Latest opened room</label>
|
||||
</entry>
|
||||
<entry name="ColorScheme" type="String">
|
||||
<label>Color scheme</label>
|
||||
</entry>
|
||||
<entry name="Blur" type="bool">
|
||||
<label>Make NeoChat blurry</label>
|
||||
<default>false</default>
|
||||
@@ -89,6 +86,10 @@
|
||||
<label>Show deleted messages in the timeline</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="HideImages" type="bool">
|
||||
<label>Hide images in the timeline</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="ShowLinkPreview" type="bool">
|
||||
<label>Show preview of the links in the chat messages</label>
|
||||
</entry>
|
||||
@@ -187,5 +188,11 @@
|
||||
<default>false</default>
|
||||
</entry>
|
||||
</group>
|
||||
<group name="Security">
|
||||
<entry name="RejectUnknownInvites" type="bool">
|
||||
<label>Reject unknown invites</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
# SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
File=neochatconfig.kcfg
|
||||
ClassName=NeoChatConfig
|
||||
Mutators=true
|
||||
DefaultValueGetters=true
|
||||
GenerateProperties=true
|
||||
ParentInConstructor=true
|
||||
Singleton=true
|
||||
@@ -9,14 +9,12 @@
|
||||
#include "controller.h"
|
||||
#include "jobs/neochatchangepasswordjob.h"
|
||||
#include "jobs/neochatdeactivateaccountjob.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "roommanager.h"
|
||||
#include "spacehierarchycache.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <qt6keychain/keychain.h>
|
||||
@@ -25,6 +23,7 @@
|
||||
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/csapi/versions.h>
|
||||
#include <Quotient/database.h>
|
||||
#include <Quotient/jobs/downloadfilejob.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
@@ -45,6 +44,7 @@ NeoChatConnection::NeoChatConnection(QObject *parent)
|
||||
: Connection(parent)
|
||||
, m_threePIdModel(new ThreePIdModel(this))
|
||||
{
|
||||
m_linkPreviewers.setMaxCost(20);
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
||||
: Connection(server, parent)
|
||||
, m_threePIdModel(new ThreePIdModel(this))
|
||||
{
|
||||
m_linkPreviewers.setMaxCost(20);
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
@@ -132,6 +133,21 @@ void NeoChatConnection::connectSignals()
|
||||
Q_EMIT homeNotificationsChanged();
|
||||
Q_EMIT homeHaveHighlightNotificationsChanged();
|
||||
});
|
||||
|
||||
// Fetch unstable features
|
||||
// TODO: Expose unstableFeatures() in libQuotient
|
||||
connect(
|
||||
this,
|
||||
&Connection::connected,
|
||||
this,
|
||||
[this] {
|
||||
auto job = callApi<GetVersionsJob>(BackgroundRequest);
|
||||
connect(job, &GetVersionsJob::success, this, [this, job] {
|
||||
m_canCheckMutualRooms = job->unstableFeatures().contains("uk.half-shot.msc2666.query_mutual_rooms"_ls);
|
||||
Q_EMIT canCheckMutualRoomsChanged();
|
||||
});
|
||||
},
|
||||
Qt::SingleShotConnection);
|
||||
}
|
||||
|
||||
int NeoChatConnection::badgeNotificationCount() const
|
||||
@@ -200,6 +216,11 @@ QVariantList NeoChatConnection::getSupportedRoomVersions() const
|
||||
return supportedRoomVersions;
|
||||
}
|
||||
|
||||
bool NeoChatConnection::canCheckMutualRooms() const
|
||||
{
|
||||
return m_canCheckMutualRooms;
|
||||
}
|
||||
|
||||
void NeoChatConnection::changePassword(const QString ¤tPassword, const QString &newPassword)
|
||||
{
|
||||
auto job = callApi<NeochatChangePasswordJob>(newPassword, false);
|
||||
@@ -543,14 +564,31 @@ LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto previewer = m_linkPreviewers.value(link, nullptr);
|
||||
auto previewer = m_linkPreviewers.object(link);
|
||||
if (previewer != nullptr) {
|
||||
return previewer;
|
||||
}
|
||||
|
||||
previewer = new LinkPreviewer(link, this);
|
||||
m_linkPreviewers[link] = previewer;
|
||||
m_linkPreviewers.insert(link, previewer);
|
||||
return previewer;
|
||||
}
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
KeyImport::Error NeoChatConnection::exportMegolmSessions(const QString &passphrase, const QString &path)
|
||||
{
|
||||
KeyImport keyImport;
|
||||
auto result = keyImport.exportKeys(passphrase, this);
|
||||
if (!result.has_value()) {
|
||||
return result.error();
|
||||
}
|
||||
QUrl url(path);
|
||||
QFile file(url.toLocalFile());
|
||||
file.open(QFile::WriteOnly);
|
||||
file.write(result.value());
|
||||
file.close();
|
||||
return KeyImport::Success;
|
||||
}
|
||||
#endif
|
||||
|
||||
#include "moc_neochatconnection.cpp"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user