Compare commits
8 Commits
work/nvrwh
...
v24.07.80
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bb55eece7 | ||
|
|
2877e40647 | ||
|
|
768ec242fa | ||
|
|
194751627f | ||
|
|
bc701c51d9 | ||
|
|
35939b4af4 | ||
|
|
a178b8b6ca | ||
|
|
a6994318de |
@@ -2,6 +2,5 @@
|
||||
; SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
[BlueprintSettings]
|
||||
kde/applications/neochat.packageAppx=True
|
||||
kde/frameworks/extra-cmake-modules.version=master
|
||||
libs/qt.qtMajorVersion=6
|
||||
|
||||
@@ -13,4 +13,3 @@ 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 "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "07")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "80")
|
||||
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.4")
|
||||
set(KF_MIN_VERSION "6.0")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
@@ -38,7 +38,6 @@ include(KDEGitCommitHooks)
|
||||
include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
include(ECMAddAndroidApk)
|
||||
include(ECMQmlModule)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -60,6 +59,7 @@ 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,13 +107,8 @@ 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,11 +29,15 @@ 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();
|
||||
@@ -41,6 +45,7 @@ private Q_SLOTS:
|
||||
void time();
|
||||
void nullTime();
|
||||
void timeString();
|
||||
void nullTimeString();
|
||||
void highlighted();
|
||||
void nullHighlighted();
|
||||
void hidden();
|
||||
@@ -62,6 +67,10 @@ private Q_SLOTS:
|
||||
void nullReplyId();
|
||||
void replyAuthor();
|
||||
void nullReplyAuthor();
|
||||
void replyBody();
|
||||
void nullReplyBody();
|
||||
void replyMediaInfo();
|
||||
void nullReplyMediaInfo();
|
||||
void thread();
|
||||
void nullThread();
|
||||
void location();
|
||||
@@ -76,136 +85,182 @@ void EventHandlerTest::initTestCase()
|
||||
|
||||
void EventHandlerTest::eventId()
|
||||
{
|
||||
QCOMPARE(EventHandler::id(room->messageEvents().at(0).get()), QStringLiteral("$153456789:example.org"));
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandler.getId(), QStringLiteral("$153456789:example.org"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullEventId()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "id called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::id(nullptr), QString());
|
||||
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());
|
||||
}
|
||||
|
||||
void EventHandlerTest::authorDisplayName()
|
||||
{
|
||||
QCOMPARE(EventHandler::authorDisplayName(room, room->messageEvents().at(1).get()), QStringLiteral("before"));
|
||||
EventHandler eventHandler(room, room->messageEvents().at(1).get());
|
||||
QCOMPARE(eventHandler.getAuthorDisplayName(), QStringLiteral("before"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullAuthorDisplayName()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "authorDisplayName called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::authorDisplayName(nullptr, nullptr), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getAuthorDisplayName(), QString());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "authorDisplayName called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::authorDisplayName(room, nullptr), QString());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getAuthorDisplayName(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::singleLineSidplayName()
|
||||
{
|
||||
QCOMPARE(EventHandler::singleLineAuthorDisplayname(room, room->messageEvents().at(11).get()),
|
||||
QStringLiteral("Look at me I put newlines in my display name"));
|
||||
EventHandler eventHandler(room, room->messageEvents().at(11).get());
|
||||
QCOMPARE(eventHandler.singleLineAuthorDisplayname(), QStringLiteral("Look at me I put newlines in my display name"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullSingleLineDisplayName()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "singleLineAuthorDisplayname called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::singleLineAuthorDisplayname(nullptr, nullptr), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.singleLineAuthorDisplayname(), QString());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "singleLineAuthorDisplayname called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::singleLineAuthorDisplayname(room, nullptr), QString());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getAuthorDisplayName called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.singleLineAuthorDisplayname(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::time()
|
||||
{
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
|
||||
QCOMPARE(EventHandler::time(event), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
|
||||
QCOMPARE(EventHandler::time(event, true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
|
||||
QCOMPARE(eventHandler.getTime(), QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC));
|
||||
QCOMPARE(eventHandler.getTime(true, QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC)), QDateTime::fromMSecsSinceEpoch(1234, Qt::UTC));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullTime()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "time called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::time(nullptr), QDateTime());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getTime called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getTime(), 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::time(room->messageEvents().at(0).get(), true), QDateTime());
|
||||
QCOMPARE(eventHandler.getTime(true), QDateTime());
|
||||
}
|
||||
|
||||
void EventHandlerTest::timeString()
|
||||
{
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
|
||||
KFormat format;
|
||||
|
||||
QCOMPARE(EventHandler::timeString(event, false),
|
||||
QCOMPARE(eventHandler.getTimeString(false),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, true),
|
||||
QCOMPARE(eventHandler.getTimeString(true),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QCOMPARE(eventHandler.getTimeString(false, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QCOMPARE(eventHandler.getTimeString(true, QLocale::ShortFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::ShortFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QCOMPARE(eventHandler.getTimeString(false, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QLocale().toString(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().time(), QLocale::LongFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
QCOMPARE(eventHandler.getTimeString(true, QLocale::LongFormat, true, QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC)),
|
||||
format.formatRelativeDate(QDateTime::fromMSecsSinceEpoch(1690699214545, Qt::UTC).toLocalTime().date(), QLocale::LongFormat));
|
||||
QCOMPARE(EventHandler::timeString(event, QStringLiteral("hh:mm")),
|
||||
QDateTime::fromMSecsSinceEpoch(1432735824654, Qt::UTC).toString(QStringLiteral("hh:mm")));
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
void EventHandlerTest::highlighted()
|
||||
{
|
||||
QCOMPARE(EventHandler::isHighlighted(room, room->messageEvents().at(2).get()), true);
|
||||
QCOMPARE(EventHandler::isHighlighted(room, room->messageEvents().at(0).get()), false);
|
||||
EventHandler eventHandlerHighlight(room, room->messageEvents().at(2).get());
|
||||
QCOMPARE(eventHandlerHighlight.isHighlighted(), true);
|
||||
|
||||
EventHandler eventHandlerNoHighlight(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoHighlight.isHighlighted(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHighlighted()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::isHighlighted(nullptr, nullptr), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isHighlighted(), false);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::isHighlighted(room, nullptr), false);
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHighlighted called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.isHighlighted(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::hidden()
|
||||
{
|
||||
QCOMPARE(EventHandler::isHidden(room, room->messageEvents().at(3).get()), true);
|
||||
QCOMPARE(EventHandler::isHidden(room, room->messageEvents().at(0).get()), false);
|
||||
EventHandler eventHandlerHidden(room, room->messageEvents().at(3).get());
|
||||
QCOMPARE(eventHandlerHidden.isHidden(), true);
|
||||
|
||||
EventHandler eventHandlerNoHidden(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoHidden.isHidden(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHidden()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::isHidden(nullptr, nullptr), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isHidden(), false);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::isHidden(room, nullptr), false);
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isHidden called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.isHidden(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::body()
|
||||
{
|
||||
const auto event = room->messageEvents().at(0).get();
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
|
||||
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"));
|
||||
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"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullBody()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "richBody called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::richBody(nullptr, nullptr), QString());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "richBody called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::richBody(room, nullptr), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "getRichBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getRichBody(), 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());
|
||||
QTest::ignoreMessage(QtWarningMsg, "getPlainBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getPlainBody(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::genericBody_data()
|
||||
@@ -225,45 +280,54 @@ void EventHandlerTest::genericBody()
|
||||
QFETCH(int, eventNum);
|
||||
QFETCH(QString, output);
|
||||
|
||||
QCOMPARE(EventHandler::genericBody(room->messageEvents().at(eventNum).get()), output);
|
||||
EventHandler eventHandler(room, room->messageEvents().at(eventNum).get());
|
||||
|
||||
QCOMPARE(eventHandler.getGenericBody(), output);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullGenericBody()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "genericBody called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::genericBody(nullptr), QString());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getGenericBody called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getGenericBody(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::markdownBody()
|
||||
{
|
||||
QCOMPARE(EventHandler::markdownBody(room->messageEvents().at(0).get()), QStringLiteral("This is an example\ntext message"));
|
||||
EventHandler eventHandler(room, room->messageEvents().at(0).get());
|
||||
|
||||
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::markdownBodyReply()
|
||||
{
|
||||
QCOMPARE(EventHandler::markdownBody(room->messageEvents().at(5).get()), QStringLiteral("reply"));
|
||||
EventHandler eventHandler(room, room->messageEvents().at(5).get());
|
||||
|
||||
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("reply"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::subtitle()
|
||||
{
|
||||
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"));
|
||||
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"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullSubtitle()
|
||||
{
|
||||
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());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "subtitleText called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.subtitleText(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::mediaInfo()
|
||||
{
|
||||
auto event = room->messageEvents().at(4).get();
|
||||
auto mediaInfo = EventHandler::mediaInfo(room, event);
|
||||
EventHandler eventHandler(room, event);
|
||||
|
||||
auto mediaInfo = eventHandler.getMediaInfo();
|
||||
auto thumbnailInfo = mediaInfo["tempInfo"_ls].toMap();
|
||||
|
||||
QCOMPARE(mediaInfo["source"_ls], room->makeMediaUrl(event->id(), QUrl("mxc://kde.org/1234567"_ls)));
|
||||
@@ -283,42 +347,53 @@ void EventHandlerTest::mediaInfo()
|
||||
|
||||
void EventHandlerTest::nullMediaInfo()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "mediaInfo called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::mediaInfo(nullptr, nullptr), QVariantMap());
|
||||
QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getMediaInfo(), QVariantMap());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "mediaInfo called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::mediaInfo(room, nullptr), QVariantMap());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getMediaInfo called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
|
||||
}
|
||||
|
||||
void EventHandlerTest::hasReply()
|
||||
{
|
||||
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(5).get()), true);
|
||||
QCOMPARE(EventHandler::hasReply(room->messageEvents().at(0).get()), false);
|
||||
EventHandler eventHandlerReply(room, room->messageEvents().at(5).get());
|
||||
QCOMPARE(eventHandlerReply.hasReply(), true);
|
||||
|
||||
EventHandler eventHandlerNoReply(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoReply.hasReply(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullHasReply()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "hasReply called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::hasReply(nullptr), false);
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "hasReply called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.hasReply(), false);
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyId()
|
||||
{
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(5).get()), QStringLiteral("$153456789:example.org"));
|
||||
QCOMPARE(EventHandler::replyId(room->messageEvents().at(0).get()), QStringLiteral(""));
|
||||
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(""));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyId()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "replyId called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::replyId(nullptr), QString());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyId called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.getReplyId(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::replyAuthor()
|
||||
{
|
||||
auto replyEvent = room->messageEvents().at(0).get();
|
||||
auto replyAuthor = room->member(replyEvent->senderId());
|
||||
auto eventHandlerReplyAuthor = EventHandler::replyAuthor(room, room->messageEvents().at(5).get());
|
||||
EventHandler eventHandler(room, room->messageEvents().at(5).get());
|
||||
|
||||
auto eventHandlerReplyAuthor = eventHandler.getReplyAuthor();
|
||||
|
||||
QCOMPARE(eventHandlerReplyAuthor.isLocalMember(), replyAuthor.id() == room->localMember().id());
|
||||
QCOMPARE(eventHandlerReplyAuthor.id(), replyAuthor.id());
|
||||
@@ -327,58 +402,121 @@ void EventHandlerTest::replyAuthor()
|
||||
QCOMPARE(eventHandlerReplyAuthor.avatarMediaId(), replyAuthor.avatarMediaId());
|
||||
QCOMPARE(eventHandlerReplyAuthor.color(), replyAuthor.color());
|
||||
|
||||
QCOMPARE(EventHandler::replyAuthor(room, room->messageEvents().at(0).get()), RoomMember());
|
||||
EventHandler eventHandlerNoAuthor(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoAuthor.getReplyAuthor(), RoomMember());
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullReplyAuthor()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "replyAuthor called with room set to nullptr.");
|
||||
QCOMPARE(EventHandler::replyAuthor(nullptr, nullptr), RoomMember());
|
||||
QTest::ignoreMessage(QtWarningMsg, "getReplyAuthor called with m_room set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getReplyAuthor(), RoomMember());
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "replyAuthor called with event set to nullptr. Returning empty user.");
|
||||
QCOMPARE(EventHandler::replyAuthor(room, 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());
|
||||
}
|
||||
|
||||
void EventHandlerTest::thread()
|
||||
{
|
||||
QCOMPARE(EventHandler::isThreaded(room->messageEvents().at(0).get()), false);
|
||||
QCOMPARE(EventHandler::threadRoot(room->messageEvents().at(0).get()), QString());
|
||||
EventHandler eventHandlerNoThread(room, room->messageEvents().at(0).get());
|
||||
QCOMPARE(eventHandlerNoThread.isThreaded(), false);
|
||||
QCOMPARE(eventHandlerNoThread.threadRoot(), QString());
|
||||
|
||||
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 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(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"));
|
||||
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"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullThread()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::isThreaded(nullptr), false);
|
||||
QTest::ignoreMessage(QtWarningMsg, "isThreaded called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.isThreaded(), false);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::threadRoot(nullptr), QString());
|
||||
EventHandler noEventHandler(room, nullptr);
|
||||
QTest::ignoreMessage(QtWarningMsg, "threadRoot called with m_event set to nullptr.");
|
||||
QCOMPARE(noEventHandler.threadRoot(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::location()
|
||||
{
|
||||
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"));
|
||||
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"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::nullLocation()
|
||||
{
|
||||
QTest::ignoreMessage(QtWarningMsg, "latitude called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::latitude(nullptr), -100.0);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getLatitude called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getLatitude(), -100.0);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "longitude called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::longitude(nullptr), -200.0);
|
||||
QTest::ignoreMessage(QtWarningMsg, "getLongitude called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getLongitude(), -200.0);
|
||||
|
||||
QTest::ignoreMessage(QtWarningMsg, "locationAssetType called with event set to nullptr.");
|
||||
QCOMPARE(EventHandler::locationAssetType(nullptr), QString());
|
||||
QTest::ignoreMessage(QtWarningMsg, "getLocationAssetType called with m_event set to nullptr.");
|
||||
QCOMPARE(emptyHandler.getLocationAssetType(), QString());
|
||||
}
|
||||
|
||||
QTEST_MAIN(EventHandlerTest)
|
||||
|
||||
@@ -274,7 +274,6 @@ 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()
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
<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>
|
||||
@@ -62,7 +61,6 @@
|
||||
<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>
|
||||
@@ -91,8 +89,6 @@
|
||||
<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>
|
||||
@@ -118,7 +114,6 @@
|
||||
<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>
|
||||
@@ -147,7 +142,6 @@
|
||||
<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>
|
||||
@@ -178,7 +172,6 @@
|
||||
<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>
|
||||
@@ -207,7 +200,6 @@
|
||||
<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>
|
||||
@@ -237,7 +229,6 @@
|
||||
<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>
|
||||
@@ -301,7 +292,6 @@
|
||||
<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>
|
||||
@@ -333,8 +323,6 @@
|
||||
<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>
|
||||
@@ -370,7 +358,6 @@
|
||||
<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>
|
||||
@@ -404,7 +391,6 @@
|
||||
<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>
|
||||
@@ -429,7 +415,6 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<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,7 +18,6 @@ Name[eu]=NeoChat
|
||||
Name[fi]=NeoChat
|
||||
Name[fr]=NeoChat
|
||||
Name[gl]=NeoChat
|
||||
Name[he]=NeoChat
|
||||
Name[hu]=NeoChat
|
||||
Name[ia]=Neochat
|
||||
Name[id]=NeoChat
|
||||
@@ -60,7 +59,6 @@ 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
|
||||
@@ -101,7 +99,6 @@ 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
|
||||
|
||||
756
po/ar/neochat.po
756
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
746
po/az/neochat.po
746
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
886
po/ca/neochat.po
886
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
727
po/cs/neochat.po
727
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
723
po/da/neochat.po
723
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
731
po/de/neochat.po
731
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
737
po/el/neochat.po
737
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
701
po/eo/neochat.po
701
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
717
po/es/neochat.po
717
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
718
po/eu/neochat.po
718
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
743
po/fi/neochat.po
743
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
726
po/fr/neochat.po
726
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
5643
po/gl/neochat.po
5643
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
1079
po/hu/neochat.po
1079
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
716
po/ia/neochat.po
716
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
731
po/id/neochat.po
731
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
716
po/ie/neochat.po
716
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
716
po/it/neochat.po
716
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
671
po/ja/neochat.po
671
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
716
po/ka/neochat.po
716
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
743
po/ko/neochat.po
743
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
671
po/lt/neochat.po
671
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
733
po/lv/neochat.po
733
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
716
po/nl/neochat.po
716
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
695
po/nn/neochat.po
695
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
740
po/pa/neochat.po
740
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
715
po/pl/neochat.po
715
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
731
po/pt/neochat.po
731
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
732
po/ru/neochat.po
732
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
746
po/sk/neochat.po
746
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
710
po/sl/neochat.po
710
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
712
po/sv/neochat.po
712
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
742
po/ta/neochat.po
742
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
717
po/tr/neochat.po
717
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
720
po/uk/neochat.po
720
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -144,6 +144,8 @@ add_library(neochat STATIC
|
||||
roomlastmessageprovider.h
|
||||
chatbarcache.cpp
|
||||
chatbarcache.h
|
||||
colorschemer.cpp
|
||||
colorschemer.h
|
||||
models/notificationsmodel.cpp
|
||||
models/notificationsmodel.h
|
||||
models/timelinemodel.cpp
|
||||
@@ -188,10 +190,6 @@ 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
|
||||
@@ -288,7 +286,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
qml/ConsentDialog.qml
|
||||
qml/AskDirectChatConfirmation.qml
|
||||
qml/HoverLinkIndicator.qml
|
||||
qml/AvatarNotification.qml
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
QtQuick
|
||||
@@ -426,7 +423,7 @@ if (TARGET KF6::Crash)
|
||||
target_link_libraries(neochat PUBLIC KF6::Crash)
|
||||
endif()
|
||||
|
||||
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)
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
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(chatBarCache->text(), handledText, chatBarCache);
|
||||
handleMessage(m_room->mainCache()->text(), handledText, chatBarCache);
|
||||
}
|
||||
|
||||
QString ActionsHandler::handleMentions(QString handledText, QList<Mention> *mentions)
|
||||
|
||||
@@ -219,7 +219,7 @@ QQC2.Control {
|
||||
}
|
||||
|
||||
onTextChanged: {
|
||||
if (!repeatTimer.running && NeoChatConfig.typingNotifications) {
|
||||
if (!repeatTimer.running && Config.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: NeoChatConfig.compactLayout ? 100 : 85
|
||||
maxWidth: NeoChatConfig.compactLayout ? -1 : Kirigami.Units.gridUnit * 60
|
||||
endPercentWidth: Config.compactLayout ? 100 : 85
|
||||
maxWidth: Config.compactLayout ? -1 : Kirigami.Units.gridUnit * 60
|
||||
|
||||
parentWidth: root.width
|
||||
}
|
||||
@@ -395,7 +395,7 @@ QQC2.Control {
|
||||
repeatTimer.stop();
|
||||
root.currentRoom.markAllMessagesAsRead();
|
||||
textField.clear();
|
||||
_private.chatBarCache.clearRelations();
|
||||
_private.chatBarCache.replyId = "";
|
||||
messageSent();
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,8 @@ QString ChatBarCache::relationMessage() const
|
||||
}
|
||||
|
||||
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
|
||||
return EventHandler::markdownBody(&**event);
|
||||
EventHandler eventhandler(room, &**event);
|
||||
return eventhandler.getMarkdownBody();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -139,8 +140,8 @@ void ChatBarCache::setThreadId(const QString &threadId)
|
||||
if (m_threadId == threadId) {
|
||||
return;
|
||||
}
|
||||
const auto oldThreadId = std::exchange(m_threadId, threadId);
|
||||
Q_EMIT threadIdChanged(oldThreadId, m_threadId);
|
||||
m_threadId = threadId;
|
||||
Q_EMIT threadIdChanged();
|
||||
}
|
||||
|
||||
QString ChatBarCache::attachmentPath() const
|
||||
@@ -160,16 +161,6 @@ 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. ChatBarComponent.
|
||||
* necessarily have to use the ChatBar component, e.g. MessageEditComponent.
|
||||
*
|
||||
* This object is intended to allow the current contents of a chat bar to be cached
|
||||
* between different rooms, i.e. there is an expectation that each NeoChatRoom could
|
||||
@@ -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, ChatBarComponent, NeoChatRoom
|
||||
* @sa ChatBar, MessageEditComponent, NeoChatRoom
|
||||
*/
|
||||
class ChatBarCache : public QObject
|
||||
{
|
||||
@@ -165,13 +165,6 @@ 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.
|
||||
*/
|
||||
@@ -195,7 +188,7 @@ public:
|
||||
Q_SIGNALS:
|
||||
void textChanged();
|
||||
void relationIdChanged(const QString &oldEventId, const QString &newEventId);
|
||||
void threadIdChanged(const QString &oldThreadId, const QString &newThreadId);
|
||||
void threadIdChanged();
|
||||
void attachmentPathChanged();
|
||||
|
||||
private:
|
||||
|
||||
48
src/colorschemer.cpp
Normal file
48
src/colorschemer.cpp
Normal file
@@ -0,0 +1,48 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#include <KColorSchemeManager>
|
||||
#include <QAbstractItemModel>
|
||||
|
||||
#include "colorschemer.h"
|
||||
|
||||
ColorSchemer::ColorSchemer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, c(new KColorSchemeManager(this))
|
||||
{
|
||||
}
|
||||
|
||||
ColorSchemer::~ColorSchemer()
|
||||
{
|
||||
}
|
||||
|
||||
QAbstractItemModel *ColorSchemer::model() const
|
||||
{
|
||||
return c->model();
|
||||
}
|
||||
|
||||
void ColorSchemer::apply(int idx)
|
||||
{
|
||||
c->activateScheme(c->model()->index(idx, 0));
|
||||
}
|
||||
|
||||
void ColorSchemer::apply(const QString &name)
|
||||
{
|
||||
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();
|
||||
}
|
||||
|
||||
#include "moc_colorschemer.cpp"
|
||||
65
src/colorschemer.h
Normal file
65
src/colorschemer.h
Normal file
@@ -0,0 +1,65 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
class QAbstractItemModel;
|
||||
class KColorSchemeManager;
|
||||
|
||||
/**
|
||||
* @class ColorSchemer
|
||||
*
|
||||
* A class to provide a wrapper around KColorSchemeManager to make it available in
|
||||
* QML.
|
||||
*
|
||||
* @sa KColorSchemeManager
|
||||
*/
|
||||
class ColorSchemer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
/**
|
||||
* @brief A QAbstractItemModel of all available color schemes.
|
||||
*
|
||||
* @sa QAbstractItemModel
|
||||
*/
|
||||
Q_PROPERTY(QAbstractItemModel *model READ model CONSTANT)
|
||||
|
||||
public:
|
||||
explicit ColorSchemer(QObject *parent = nullptr);
|
||||
~ColorSchemer();
|
||||
|
||||
QAbstractItemModel *model() const;
|
||||
|
||||
/**
|
||||
* @brief Activates the KColorScheme identified by the provided index.
|
||||
*
|
||||
* @sa KColorScheme
|
||||
*/
|
||||
Q_INVOKABLE void apply(int idx);
|
||||
|
||||
/**
|
||||
* @brief Activates the KColorScheme with the given name.
|
||||
*
|
||||
* @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;
|
||||
|
||||
private:
|
||||
KColorSchemeManager *c;
|
||||
};
|
||||
@@ -13,11 +13,13 @@
|
||||
|
||||
#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"
|
||||
@@ -62,11 +64,7 @@ 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();
|
||||
@@ -204,33 +202,15 @@ void Controller::invokeLogin()
|
||||
m_connectionsLoading[accountId] = connection;
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
||||
connection->loadState();
|
||||
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);
|
||||
}
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
});
|
||||
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
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -271,19 +251,23 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QStrin
|
||||
return job;
|
||||
}
|
||||
|
||||
void Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken)
|
||||
bool Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken)
|
||||
{
|
||||
qDebug() << "Save the access token to the keychain for " << userId;
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
bool Controller::supportSystemTray() const
|
||||
@@ -425,28 +409,11 @@ void Controller::removeConnection(const QString &userId)
|
||||
|
||||
bool Controller::csSupported() const
|
||||
{
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
#if Quotient_VERSION_MINOR > 9
|
||||
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.
|
||||
*/
|
||||
void saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
|
||||
bool saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
|
||||
|
||||
[[nodiscard]] bool supportSystemTray() const;
|
||||
|
||||
@@ -108,17 +108,6 @@ 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);
|
||||
|
||||
@@ -127,11 +116,13 @@ 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: NeoChatConfig.showAllEvents
|
||||
checked: Config.showAllEvents
|
||||
|
||||
onToggled: NeoChatConfig.showAllEvents = checked
|
||||
onToggled: Config.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: NeoChatConfig.alwaysVerifyDevice
|
||||
checked: Config.alwaysVerifyDevice
|
||||
|
||||
onToggled: NeoChatConfig.alwaysVerifyDevice = checked
|
||||
onToggled: Config.alwaysVerifyDevice = checked
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
text: i18nc("@option:check", "Show focus in window header")
|
||||
checked: NeoChatConfig.windowTitleFocus
|
||||
checked: Config.windowTitleFocus
|
||||
|
||||
onToggled: {
|
||||
NeoChatConfig.windowTitleFocus = checked;
|
||||
NeoChatConfig.save();
|
||||
Config.windowTitleFocus = checked;
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,23 +18,23 @@ FormCard.FormCardPage {
|
||||
FormCard.FormCheckDelegate {
|
||||
id: roomAccountDataVisibleCheck
|
||||
text: i18nc("@option:check Enable the matrix 'threads' feature", "Threads")
|
||||
checked: NeoChatConfig.threads
|
||||
checked: Config.threads
|
||||
|
||||
onToggled: NeoChatConfig.threads = checked
|
||||
onToggled: Config.threads = checked
|
||||
}
|
||||
FormCard.FormCheckDelegate {
|
||||
text: i18nc("@option:check Enable the matrix 'secret backup' feature", "Secret Backup")
|
||||
checked: NeoChatConfig.secretBackup
|
||||
checked: Config.secretBackup
|
||||
|
||||
onToggled: NeoChatConfig.secretBackup = checked
|
||||
onToggled: Config.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: NeoChatConfig.phone3PId
|
||||
checked: Config.phone3PId
|
||||
|
||||
onToggled: {
|
||||
NeoChatConfig.phone3PId = checked
|
||||
NeoChatConfig.save();
|
||||
Config.phone3PId = checked
|
||||
Config.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,8 +38,6 @@ 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. */
|
||||
ChatBar, /**< A text edit for editing a message. */
|
||||
Edit, /**< 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,17 +5,16 @@
|
||||
|
||||
#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>
|
||||
@@ -26,6 +25,9 @@
|
||||
#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"
|
||||
@@ -33,52 +35,82 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
QString EventHandler::id(const Quotient::RoomEvent *event)
|
||||
EventHandler::EventHandler(const NeoChatRoom *room, const RoomEvent *event)
|
||||
: m_room(room)
|
||||
, m_event(event)
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "id called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
return !event->id().isEmpty() ? event->id() : event->transactionId();
|
||||
}
|
||||
|
||||
QString EventHandler::authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
|
||||
QString EventHandler::getId() const
|
||||
{
|
||||
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.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getId called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
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();
|
||||
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 (previousDisplayName.isEmpty()) {
|
||||
previousDisplayName = event->senderId();
|
||||
previousDisplayName = m_event->senderId();
|
||||
}
|
||||
return previousDisplayName;
|
||||
} else {
|
||||
const auto author = isPending ? room->localMember() : room->member(event->senderId());
|
||||
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
|
||||
return author.htmlSafeDisplayName();
|
||||
}
|
||||
}
|
||||
|
||||
QString EventHandler::singleLineAuthorDisplayname(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending)
|
||||
QString EventHandler::singleLineAuthorDisplayname(bool isPending) const
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "singleLineAuthorDisplayname called with room set to nullptr.";
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getAuthorDisplayName called with m_room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "singleLineAuthorDisplayname called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getAuthorDisplayName called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto author = isPending ? room->localMember() : room->member(event->senderId());
|
||||
const auto author = isPending ? m_room->localMember() : m_room->member(m_event->senderId());
|
||||
auto displayName = author.displayName();
|
||||
displayName.replace(QStringLiteral("<br>\n"), QStringLiteral(" "));
|
||||
displayName.replace(QStringLiteral("<br>"), QStringLiteral(" "));
|
||||
@@ -89,10 +121,10 @@ QString EventHandler::singleLineAuthorDisplayname(const NeoChatRoom *room, const
|
||||
return displayName;
|
||||
}
|
||||
|
||||
QDateTime EventHandler::time(const Quotient::RoomEvent *event, bool isPending, QDateTime lastUpdated)
|
||||
QDateTime EventHandler::getTime(bool isPending, QDateTime lastUpdated) const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "time called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getTime called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (isPending && lastUpdated == QDateTime()) {
|
||||
@@ -100,16 +132,24 @@ QDateTime EventHandler::time(const Quotient::RoomEvent *event, bool isPending, Q
|
||||
return {};
|
||||
}
|
||||
|
||||
return isPending ? lastUpdated : event->originTimestamp();
|
||||
return isPending ? lastUpdated : m_event->originTimestamp();
|
||||
}
|
||||
|
||||
QString EventHandler::timeString(const Quotient::RoomEvent *event, bool relative, QLocale::FormatType format, bool isPending, QDateTime lastUpdated)
|
||||
QString EventHandler::getTimeString(bool relative, QLocale::FormatType format, bool isPending, QDateTime lastUpdated) const
|
||||
{
|
||||
auto ts = time(event, isPending, 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);
|
||||
if (ts.isValid()) {
|
||||
if (relative) {
|
||||
KFormat formatter;
|
||||
return formatter.formatRelativeDate(ts.toLocalTime().date(), format);
|
||||
return m_format.formatRelativeDate(ts.toLocalTime().date(), format);
|
||||
} else {
|
||||
return QLocale().toString(ts.toLocalTime().time(), format);
|
||||
}
|
||||
@@ -117,41 +157,36 @@ QString EventHandler::timeString(const Quotient::RoomEvent *event, bool relative
|
||||
return {};
|
||||
}
|
||||
|
||||
QString EventHandler::timeString(const Quotient::RoomEvent *event, const QString &format, bool isPending, const QDateTime &lastUpdated)
|
||||
bool EventHandler::isHighlighted()
|
||||
{
|
||||
return time(event, isPending, lastUpdated).toLocalTime().toString(format);
|
||||
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);
|
||||
}
|
||||
|
||||
bool EventHandler::isHighlighted(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
bool EventHandler::isHidden()
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "isHighlighted called with room set to nullptr.";
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "isHidden called with m_room set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "isHighlighted called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "isHidden called with m_event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
|
||||
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()) {
|
||||
if (m_event->isStateEvent() && !NeoChatConfig::self()->showStateEvent()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
|
||||
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(m_event)) {
|
||||
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::self()->showLeaveJoinEvent()) {
|
||||
return true;
|
||||
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::self()->showRename()) {
|
||||
@@ -162,33 +197,33 @@ bool EventHandler::isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *
|
||||
}
|
||||
}
|
||||
|
||||
if (event->isStateEvent() && eventCast<const StateEvent>(event)->repeatsState()) {
|
||||
if (m_event->isStateEvent() && eventCast<const StateEvent>(m_event)->repeatsState()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// isReplacement?
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(m_event)) {
|
||||
if (!e->replacedEvent().isEmpty()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (is<RedactionEvent>(*event) || is<ReactionEvent>(*event)) {
|
||||
if (is<RedactionEvent>(*m_event) || is<ReactionEvent>(*m_event)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(m_event)) {
|
||||
if (!e->replacedEvent().isEmpty() && e->replacedEvent() != e->id()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (room->connection()->isIgnored(event->senderId())) {
|
||||
if (m_room->connection()->isIgnored(m_event->senderId())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// hide ending live location beacons
|
||||
if (event->isStateEvent() && event->matrixType() == "org.matrix.msc3672.beacon_info"_ls && !event->contentJson()["live"_ls].toBool()) {
|
||||
if (m_event->isStateEvent() && m_event->matrixType() == "org.matrix.msc3672.beacon_info"_ls && !m_event->contentJson()["live"_ls].toBool()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -227,52 +262,44 @@ QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
return body;
|
||||
}
|
||||
|
||||
QString EventHandler::richBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines)
|
||||
QString EventHandler::getRichBody(bool stripNewlines) const
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "richBody called with room set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getRichBody called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "richBody called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return getBody(room, event, Qt::RichText, stripNewlines);
|
||||
return getBody(m_event, Qt::RichText, stripNewlines);
|
||||
}
|
||||
|
||||
QString EventHandler::plainBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines)
|
||||
QString EventHandler::getPlainBody(bool stripNewlines) const
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "plainBody called with room set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getPlainBody called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "plainBody called with event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return getBody(room, event, Qt::PlainText, stripNewlines);
|
||||
return getBody(m_event, Qt::PlainText, stripNewlines);
|
||||
}
|
||||
|
||||
QString EventHandler::markdownBody(const Quotient::RoomEvent *event)
|
||||
QString EventHandler::getMarkdownBody() const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "markdownBody called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getMarkdownBody called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!event->is<RoomMessageEvent>()) {
|
||||
qCWarning(EventHandling) << "markdownBody called when event isn't a RoomMessageEvent.";
|
||||
if (!m_event->is<RoomMessageEvent>()) {
|
||||
qCWarning(EventHandling) << "getMarkdownBody called when m_event isn't a RoomMessageEvent.";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(event);
|
||||
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
|
||||
|
||||
QString plainBody = roomMessageEvent->plainBody();
|
||||
plainBody.remove(TextRegex::removeReply);
|
||||
return plainBody;
|
||||
}
|
||||
|
||||
QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines)
|
||||
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
|
||||
{
|
||||
if (event->isRedacted()) {
|
||||
auto reason = event->redactedBecause()->reason();
|
||||
@@ -283,15 +310,15 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
|
||||
return switchOnType(
|
||||
*event,
|
||||
[room, format, stripNewlines](const RoomMessageEvent &event) {
|
||||
return getMessageBody(room, event, format, stripNewlines);
|
||||
[this, format, stripNewlines](const RoomMessageEvent &event) {
|
||||
return getMessageBody(event, format, stripNewlines);
|
||||
},
|
||||
[](const StickerEvent &e) {
|
||||
return e.body();
|
||||
},
|
||||
[room, prettyPrint](const RoomMemberEvent &e) {
|
||||
[this, prettyPrint](const RoomMemberEvent &e) {
|
||||
// FIXME: Rewind to the name that was at the time of this event
|
||||
auto subjectName = room->member(e.userId()).htmlSafeDisplayName();
|
||||
auto subjectName = m_room->member(e.userId()).htmlSafeDisplayName();
|
||||
if (e.membership() == Membership::Leave) {
|
||||
if (e.prevContent() && e.prevContent()->displayName) {
|
||||
subjectName = sanitized(*e.prevContent()->displayName);
|
||||
@@ -303,7 +330,7 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
|
||||
if (prettyPrint) {
|
||||
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
|
||||
.arg(e.userId(), room->member(e.userId()).color().name(), subjectName);
|
||||
.arg(e.userId(), m_room->member(e.userId()).color().name(), subjectName);
|
||||
}
|
||||
|
||||
// The below code assumes senderName output in AuthorRole
|
||||
@@ -439,7 +466,7 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines)
|
||||
QString EventHandler::getMessageBody(const RoomMessageEvent &event, Qt::TextFormat format, bool stripNewlines) const
|
||||
{
|
||||
TextHandler textHandler;
|
||||
|
||||
@@ -471,24 +498,24 @@ QString EventHandler::getMessageBody(const NeoChatRoom *room, const RoomMessageE
|
||||
}
|
||||
|
||||
if (format == Qt::RichText) {
|
||||
return textHandler.handleRecieveRichText(inputFormat, room, &event, stripNewlines, event.isReplaced());
|
||||
return textHandler.handleRecieveRichText(inputFormat, m_room, &event, stripNewlines, event.isReplaced());
|
||||
} else {
|
||||
return textHandler.handleRecievePlainText(inputFormat, stripNewlines);
|
||||
}
|
||||
}
|
||||
|
||||
QString EventHandler::genericBody(const Quotient::RoomEvent *event)
|
||||
QString EventHandler::getGenericBody() const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "genericBody called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getGenericBody called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (event->isRedacted()) {
|
||||
if (m_event->isRedacted()) {
|
||||
return i18n("<i>[This message was deleted]</i>");
|
||||
}
|
||||
|
||||
return switchOnType(
|
||||
*event,
|
||||
*m_event,
|
||||
[](const RoomMessageEvent &e) {
|
||||
Q_UNUSED(e)
|
||||
return i18n("sent a message");
|
||||
@@ -608,33 +635,29 @@ QString EventHandler::genericBody(const Quotient::RoomEvent *event)
|
||||
i18n("Unknown event"));
|
||||
}
|
||||
|
||||
QString EventHandler::subtitleText(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
QString EventHandler::subtitleText() const
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "subtitleText called with room set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "subtitleText called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
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);
|
||||
return singleLineAuthorDisplayname() + (m_event->isStateEvent() ? QLatin1String(" ") : QLatin1String(": ")) + getPlainBody(true);
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::mediaInfo(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
QVariantMap EventHandler::getMediaInfo() const
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "mediaInfo called with room set to nullptr.";
|
||||
if (m_room == nullptr) {
|
||||
qCWarning(EventHandling) << "getMediaInfo called with m_room set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "mediaInfo called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getMediaInfo called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return getMediaInfoForEvent(room, event);
|
||||
return getMediaInfoForEvent(m_event);
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
QVariantMap EventHandler::getMediaInfoForEvent(const Quotient::RoomEvent *event) const
|
||||
{
|
||||
QString eventId = event->id();
|
||||
|
||||
@@ -647,7 +670,7 @@ QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Qu
|
||||
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
fileInfo = roomMessageEvent->content()->fileInfo();
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(room, fileInfo, eventId, false, false);
|
||||
QVariantMap mediaInfo = getMediaInfoFromFileInfo(fileInfo, eventId, false, false);
|
||||
// if filename isn't specifically given, it is in body
|
||||
// https://spec.matrix.org/latest/client-server-api/#mfile
|
||||
mediaInfo["filename"_ls] = (fileInfo->originalName.isEmpty()) ? roomMessageEvent->plainBody() : fileInfo->originalName;
|
||||
@@ -659,17 +682,13 @@ QVariantMap EventHandler::getMediaInfoForEvent(const NeoChatRoom *room, const Qu
|
||||
auto stickerEvent = eventCast<const StickerEvent>(event);
|
||||
fileInfo = &stickerEvent->image();
|
||||
|
||||
return getMediaInfoFromFileInfo(room, fileInfo, eventId, false, true);
|
||||
return getMediaInfoFromFileInfo(fileInfo, eventId, false, true);
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
const EventContent::FileInfo *fileInfo,
|
||||
const QString &eventId,
|
||||
bool isThumbnail,
|
||||
bool isSticker)
|
||||
QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail, bool isSticker) const
|
||||
{
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
@@ -677,7 +696,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
if (!fileInfo->url().isValid() || fileInfo->url().scheme() != QStringLiteral("mxc") || eventId.isEmpty()) {
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
} else {
|
||||
QUrl source = room->makeMediaUrl(eventId, fileInfo->url());
|
||||
QUrl source = m_room->makeMediaUrl(eventId, fileInfo->url());
|
||||
|
||||
if (source.isValid()) {
|
||||
mediaInfo["source"_ls] = source;
|
||||
@@ -709,7 +728,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(room, castInfo->thumbnailInfo(), eventId, true);
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
@@ -732,7 +751,7 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(room, castInfo->thumbnailInfo(), eventId, true);
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
@@ -756,89 +775,156 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const NeoChatRoom *room,
|
||||
return mediaInfo;
|
||||
}
|
||||
|
||||
bool EventHandler::hasReply(const Quotient::RoomEvent *event, bool showFallbacks)
|
||||
bool EventHandler::hasReply() const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "hasReply called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "hasReply called with m_event set to nullptr.";
|
||||
return false;
|
||||
}
|
||||
|
||||
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;
|
||||
return !m_event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString().isEmpty();
|
||||
}
|
||||
|
||||
QString EventHandler::replyId(const Quotient::RoomEvent *event)
|
||||
QString EventHandler::getReplyId() const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "replyId called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyId called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
return event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
return m_event->contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
}
|
||||
|
||||
Quotient::RoomMember EventHandler::replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event)
|
||||
MessageComponentType::Type EventHandler::replyMessageComponentType() const
|
||||
{
|
||||
if (room == nullptr) {
|
||||
qCWarning(EventHandling) << "replyAuthor called with room set to nullptr.";
|
||||
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.";
|
||||
return {};
|
||||
}
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "replyAuthor called with event set to nullptr. Returning empty user.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getReplyAuthor called with m_event set to nullptr. Returning empty user.";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (auto replyPtr = room->getReplyForEvent(*event)) {
|
||||
return room->member(replyPtr->senderId());
|
||||
if (auto replyPtr = m_room->getReplyForEvent(*m_event)) {
|
||||
return m_room->member(replyPtr->senderId());
|
||||
} else {
|
||||
return room->member(QString());
|
||||
return m_room->member(QString());
|
||||
}
|
||||
}
|
||||
|
||||
bool EventHandler::isThreaded(const Quotient::RoomEvent *event)
|
||||
QString EventHandler::getReplyRichBody(bool stripNewlines) const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "isThreaded called with event set to nullptr.";
|
||||
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.";
|
||||
return false;
|
||||
}
|
||||
|
||||
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));
|
||||
return (m_event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||
&& m_event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls)
|
||||
|| (!m_event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && m_event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls));
|
||||
}
|
||||
|
||||
QString EventHandler::threadRoot(const Quotient::RoomEvent *event)
|
||||
QString EventHandler::threadRoot() const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "threadRoot called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "threadRoot called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
// Get the thread root ID from m.relates_to if it exists.
|
||||
if (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();
|
||||
if (m_event->contentPart<QJsonObject>("m.relates_to"_ls).contains("rel_type"_ls)
|
||||
&& m_event->contentPart<QJsonObject>("m.relates_to"_ls)["rel_type"_ls].toString() == "m.thread"_ls) {
|
||||
return m_event->contentPart<QJsonObject>("m.relates_to"_ls)["event_id"_ls].toString();
|
||||
}
|
||||
// For thread root events they have an m.relations in the unsigned part with a m.thread object.
|
||||
// If so return the event ID as it is the root.
|
||||
if (!event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
|
||||
return id(event);
|
||||
if (!m_event->unsignedPart<QJsonObject>("m.relations"_ls).isEmpty() && m_event->unsignedPart<QJsonObject>("m.relations"_ls).contains("m.thread"_ls)) {
|
||||
return getId();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
float EventHandler::latitude(const Quotient::RoomEvent *event)
|
||||
float EventHandler::getLatitude() const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "latitude called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getLatitude called with m_event set to nullptr.";
|
||||
return -100.0;
|
||||
}
|
||||
|
||||
const auto geoUri = event->contentJson()["geo_uri"_ls].toString();
|
||||
const auto geoUri = m_event->contentJson()["geo_uri"_ls].toString();
|
||||
if (geoUri.isEmpty()) {
|
||||
return -100.0; // latitude runs from -90deg to +90deg so -100 is out of range.
|
||||
}
|
||||
@@ -846,14 +932,14 @@ float EventHandler::latitude(const Quotient::RoomEvent *event)
|
||||
return latitude.toFloat();
|
||||
}
|
||||
|
||||
float EventHandler::longitude(const Quotient::RoomEvent *event)
|
||||
float EventHandler::getLongitude() const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "longitude called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getLongitude called with m_event set to nullptr.";
|
||||
return -200.0;
|
||||
}
|
||||
|
||||
const auto geoUri = event->contentJson()["geo_uri"_ls].toString();
|
||||
const auto geoUri = m_event->contentJson()["geo_uri"_ls].toString();
|
||||
if (geoUri.isEmpty()) {
|
||||
return -200.0; // longitude runs from -180deg to +180deg so -200 is out of range.
|
||||
}
|
||||
@@ -861,14 +947,14 @@ float EventHandler::longitude(const Quotient::RoomEvent *event)
|
||||
return latitude.toFloat();
|
||||
}
|
||||
|
||||
QString EventHandler::locationAssetType(const Quotient::RoomEvent *event)
|
||||
QString EventHandler::getLocationAssetType() const
|
||||
{
|
||||
if (event == nullptr) {
|
||||
qCWarning(EventHandling) << "locationAssetType called with event set to nullptr.";
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getLocationAssetType called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto assetType = event->contentJson()["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
||||
const auto assetType = m_event->contentJson()["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
||||
if (assetType.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -3,22 +3,24 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QString>
|
||||
#include <Quotient/events/eventcontent.h>
|
||||
#include <QObject>
|
||||
|
||||
#include <KFormat>
|
||||
|
||||
#include <Quotient/eventitem.h>
|
||||
#include <Quotient/events/roomevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "enums/messagecomponenttype.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
namespace EventContent
|
||||
{
|
||||
class FileInfo;
|
||||
}
|
||||
class RoomEvent;
|
||||
class RoomMember;
|
||||
class RoomMessageEvent;
|
||||
}
|
||||
|
||||
class LinkPreviewer;
|
||||
class NeoChatRoom;
|
||||
class ReactionModel;
|
||||
|
||||
/**
|
||||
* @class EventHandler
|
||||
@@ -36,19 +38,40 @@ class NeoChatRoom;
|
||||
*/
|
||||
class EventHandler
|
||||
{
|
||||
Q_GADGET
|
||||
|
||||
public:
|
||||
EventHandler(const NeoChatRoom *room, const Quotient::RoomEvent *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.
|
||||
* @brief Return the Matrix ID of the event.
|
||||
*/
|
||||
static QString id(const Quotient::RoomEvent *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;
|
||||
|
||||
/**
|
||||
* @brief Get the display name of the event author.
|
||||
*
|
||||
* This method is special in that it will return
|
||||
* This method is separate from getAuthor() and 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.
|
||||
@@ -56,7 +79,7 @@ public:
|
||||
* @param isPending whether the event is pending as this cannot be derived from
|
||||
* just the event object.
|
||||
*/
|
||||
static QString authorDisplayName(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);
|
||||
QString getAuthorDisplayName(bool isPending = false) const;
|
||||
|
||||
/**
|
||||
* @brief Get the display name of the event author but with any newlines removed.
|
||||
@@ -67,12 +90,12 @@ public:
|
||||
* @param isPending whether the event is pending as this cannot be derived from
|
||||
* just the event object.
|
||||
*/
|
||||
static QString singleLineAuthorDisplayname(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool isPending = false);
|
||||
QString singleLineAuthorDisplayname(bool isPending = false) const;
|
||||
|
||||
/**
|
||||
* @brief Return a QDateTime object for the event timestamp.
|
||||
*/
|
||||
static QDateTime time(const Quotient::RoomEvent *event, bool isPending = false, QDateTime lastUpdated = {});
|
||||
QDateTime getTime(bool isPending = false, QDateTime lastUpdated = {}) const;
|
||||
|
||||
/**
|
||||
* @brief Return a QString for the event timestamp.
|
||||
@@ -88,32 +111,14 @@ public:
|
||||
* @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,
|
||||
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 = {});
|
||||
QString getTimeString(bool relative, QLocale::FormatType format = QLocale::ShortFormat, bool isPending = false, QDateTime lastUpdated = {}) const;
|
||||
|
||||
/**
|
||||
* @brief Whether the event should be highlighted in the timeline.
|
||||
*
|
||||
* @note Messages in direct chats are never highlighted.
|
||||
*/
|
||||
static bool isHighlighted(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
bool isHighlighted();
|
||||
|
||||
/**
|
||||
* @brief Whether the event should be hidden in the timeline.
|
||||
@@ -122,7 +127,7 @@ public:
|
||||
* user has hidden all state events or if the sender has been ignored by the local
|
||||
* user.
|
||||
*/
|
||||
static bool isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
bool isHidden();
|
||||
|
||||
/**
|
||||
* @brief The input format of the body in the message.
|
||||
@@ -154,7 +159,7 @@ public:
|
||||
*
|
||||
* @param stripNewlines whether the output should have new lines in it.
|
||||
*/
|
||||
static QString richBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines = false);
|
||||
QString getRichBody(bool stripNewlines = false) const;
|
||||
|
||||
/**
|
||||
* @brief Output a string for the message content ready for display in a plain text field.
|
||||
@@ -170,14 +175,14 @@ public:
|
||||
*
|
||||
* @param stripNewlines whether the output should have new lines in it.
|
||||
*/
|
||||
static QString plainBody(const NeoChatRoom *room, const Quotient::RoomEvent *event, bool stripNewlines = false);
|
||||
QString getPlainBody(bool stripNewlines = false) const;
|
||||
|
||||
/**
|
||||
* @brief Output the original body for the message content, useful for editing the original message.
|
||||
*
|
||||
* The event type must be a room message event.
|
||||
*/
|
||||
static QString markdownBody(const Quotient::RoomEvent *event);
|
||||
QString getMarkdownBody() const;
|
||||
|
||||
/**
|
||||
* @brief Output a generic string for the message content ready for display.
|
||||
@@ -190,9 +195,9 @@ public:
|
||||
* E.g. For a message the text will be:
|
||||
* "sent a message"
|
||||
*
|
||||
* @sa richBody(), plainBody()
|
||||
* @sa getRichBody(), getPlainBody()
|
||||
*/
|
||||
static QString genericBody(const Quotient::RoomEvent *event);
|
||||
QString getGenericBody() const;
|
||||
|
||||
/**
|
||||
* @brief Output a string for the event to be used as a RoomList subtitle.
|
||||
@@ -200,7 +205,7 @@ public:
|
||||
* The output includes the username followed by the plain message, all with no
|
||||
* line breaks.
|
||||
*/
|
||||
static QString subtitleText(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
QString subtitleText() const;
|
||||
|
||||
/**
|
||||
* @brief Return the media info for the event.
|
||||
@@ -218,21 +223,22 @@ 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
|
||||
*/
|
||||
static QVariantMap mediaInfo(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
QVariantMap getMediaInfo() const;
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*/
|
||||
static bool hasReply(const Quotient::RoomEvent *event, bool showFallbacks = true);
|
||||
bool hasReply() const;
|
||||
|
||||
/**
|
||||
* @brief Return the Matrix ID of the event replied to.
|
||||
*/
|
||||
static QString replyId(const Quotient::RoomEvent *event);
|
||||
QString getReplyId() const;
|
||||
|
||||
/**
|
||||
* @brief The MessageComponentType to use to visualise the reply content.
|
||||
*/
|
||||
MessageComponentType::Type replyMessageComponentType() const;
|
||||
|
||||
/**
|
||||
* @brief Get the author of the event replied to in context of the room.
|
||||
@@ -247,21 +253,73 @@ public:
|
||||
*
|
||||
* @sa Quotient::RoomMember
|
||||
*/
|
||||
static Quotient::RoomMember replyAuthor(const NeoChatRoom *room, const Quotient::RoomEvent *event);
|
||||
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;
|
||||
|
||||
/**
|
||||
* @brief Whether the message is part of a thread.
|
||||
*
|
||||
* i.e. There is a rel_type of m.thread.
|
||||
*/
|
||||
static bool isThreaded(const Quotient::RoomEvent *event);
|
||||
bool isThreaded() const;
|
||||
|
||||
/**
|
||||
* @brief Return the Matrix ID of the thread's root message.
|
||||
*
|
||||
* Empty if this not part of a thread.
|
||||
*/
|
||||
static QString threadRoot(const Quotient::RoomEvent *event);
|
||||
QString threadRoot() const;
|
||||
|
||||
/**
|
||||
* @brief Return the latitude for the event.
|
||||
@@ -269,7 +327,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).
|
||||
*/
|
||||
static float latitude(const Quotient::RoomEvent *event);
|
||||
float getLatitude() const;
|
||||
|
||||
/**
|
||||
* @brief Return the longitude for the event.
|
||||
@@ -277,21 +335,23 @@ 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).
|
||||
*/
|
||||
static float longitude(const Quotient::RoomEvent *event);
|
||||
float getLongitude() const;
|
||||
|
||||
/**
|
||||
* @brief Return the type of location marker for the event.
|
||||
*/
|
||||
static QString locationAssetType(const Quotient::RoomEvent *event);
|
||||
QString getLocationAssetType() const;
|
||||
|
||||
private:
|
||||
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);
|
||||
const NeoChatRoom *m_room = nullptr;
|
||||
const Quotient::RoomEvent *m_event = nullptr;
|
||||
|
||||
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);
|
||||
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;
|
||||
};
|
||||
|
||||
@@ -10,13 +10,22 @@
|
||||
#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)
|
||||
@@ -43,11 +52,8 @@ struct ForeignSSSSHandler {
|
||||
QML_NAMED_ELEMENT(SSSSHandler)
|
||||
};
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
struct ForeignKeyImport {
|
||||
struct RoomMemberForeign {
|
||||
Q_GADGET
|
||||
QML_SINGLETON
|
||||
QML_FOREIGN(Quotient::KeyImport)
|
||||
QML_NAMED_ELEMENT(KeyImport)
|
||||
QML_FOREIGN(Quotient::RoomMember)
|
||||
QML_NAMED_ELEMENT(RoomMember)
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -78,7 +78,9 @@ void LoginHelper::init()
|
||||
account.setHomeserver(m_connection->homeserver());
|
||||
account.setDeviceId(m_connection->deviceId());
|
||||
account.setDeviceName(m_deviceName);
|
||||
Controller::instance().saveAccessTokenToKeyChain(account.userId(), m_connection->accessToken());
|
||||
if (!Controller::instance().saveAccessTokenToKeyChain(account.userId(), m_connection->accessToken())) {
|
||||
qWarning() << "Couldn't save access token";
|
||||
}
|
||||
account.sync();
|
||||
Controller::instance().addConnection(m_connection);
|
||||
Controller::instance().setActiveConnection(m_connection);
|
||||
|
||||
@@ -37,7 +37,6 @@
|
||||
#include <KCrash>
|
||||
#endif
|
||||
|
||||
#include <KColorSchemeManager>
|
||||
#include <KLocalizedContext>
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -46,6 +45,7 @@
|
||||
#include <Quotient/networkaccessmanager.h>
|
||||
|
||||
#include "blurhashimageprovider.h"
|
||||
#include "colorschemer.h"
|
||||
#include "controller.h"
|
||||
#include "logger.h"
|
||||
#include "neochatconfig.h"
|
||||
@@ -148,7 +148,7 @@ int main(int argc, char *argv[])
|
||||
i18n("Maintainer"),
|
||||
QStringLiteral("carl@carlschwan.eu"),
|
||||
QStringLiteral("https://carlschwan.eu"),
|
||||
QUrl(QStringLiteral("https://carlschwan.eu/avatar.png")));
|
||||
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"));
|
||||
@@ -184,7 +184,10 @@ int main(int argc, char *argv[])
|
||||
QStringLiteral("/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"));
|
||||
#endif
|
||||
|
||||
KColorSchemeManager::instance();
|
||||
ColorSchemer colorScheme;
|
||||
if (!NeoChatConfig::self()->colorScheme().isEmpty()) {
|
||||
colorScheme.apply(NeoChatConfig::self()->colorScheme());
|
||||
}
|
||||
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
|
||||
|
||||
124
src/matriximageprovider.cpp
Normal file
124
src/matriximageprovider.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
// 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"
|
||||
80
src/matriximageprovider.h
Normal file
80
src/matriximageprovider.h
Normal file
@@ -0,0 +1,80 @@
|
||||
// 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;
|
||||
};
|
||||
@@ -2,7 +2,6 @@
|
||||
// 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>
|
||||
@@ -10,10 +9,9 @@
|
||||
#include <Quotient/events/redactionevent.h>
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
#include <Kirigami/Platform/PlatformTheme>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <KSyntaxHighlighting/Definition>
|
||||
@@ -21,7 +19,10 @@
|
||||
#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"
|
||||
@@ -29,20 +30,20 @@
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply, bool isPending, MessageContentModel *parent)
|
||||
: QAbstractListModel(parent)
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const Quotient::RoomEvent *event, bool isReply, bool isPending)
|
||||
: QAbstractListModel(nullptr)
|
||||
, 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, MessageContentModel *parent)
|
||||
: QAbstractListModel(parent)
|
||||
MessageContentModel::MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply, bool isPending)
|
||||
: QAbstractListModel(nullptr)
|
||||
, m_room(room)
|
||||
, m_eventId(eventId)
|
||||
, m_isPending(isPending)
|
||||
@@ -56,27 +57,15 @@ 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.
|
||||
// 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);
|
||||
Q_ASSERT(!m_eventId.isEmpty());
|
||||
|
||||
Quotient::connectUntil(m_room.get(), &NeoChatRoom::extraEventLoaded, this, [this](const QString &eventId) {
|
||||
if (m_room != nullptr) {
|
||||
if (eventId == m_eventId) {
|
||||
m_notFound = false;
|
||||
intiializeEvent(m_room->getEvent(eventId));
|
||||
m_event = loadEvent<RoomEvent>(m_room->getEvent(eventId)->fullJson());
|
||||
Q_EMIT eventUpdated();
|
||||
updateReplyModel();
|
||||
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();
|
||||
resetContent();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -84,18 +73,16 @@ void MessageContentModel::initializeModel()
|
||||
});
|
||||
|
||||
if (m_event == nullptr) {
|
||||
intiializeEvent(m_room->getEvent(m_eventId));
|
||||
if (m_event == nullptr) {
|
||||
m_room->downloadEventFromServer(m_eventId);
|
||||
}
|
||||
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() || m_eventId == serverEvent->transactionId()) {
|
||||
if (m_eventId == serverEvent->id()) {
|
||||
beginResetModel();
|
||||
m_isPending = false;
|
||||
intiializeEvent(serverEvent);
|
||||
m_event = loadEvent<RoomEvent>(serverEvent->fullJson());
|
||||
Q_EMIT eventUpdated();
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
@@ -104,8 +91,8 @@ void MessageContentModel::initializeModel()
|
||||
if (m_room != nullptr && m_event != nullptr) {
|
||||
if (m_eventId == newEvent->id()) {
|
||||
beginResetModel();
|
||||
intiializeEvent(newEvent);
|
||||
resetContent();
|
||||
m_event = loadEvent<RoomEvent>(newEvent->fullJson());
|
||||
Q_EMIT eventUpdated();
|
||||
endResetModel();
|
||||
}
|
||||
}
|
||||
@@ -140,13 +127,6 @@ 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();
|
||||
});
|
||||
@@ -168,47 +148,12 @@ 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;
|
||||
@@ -221,7 +166,7 @@ void MessageContentModel::setShowAuthor(bool showAuthor)
|
||||
}
|
||||
m_showAuthor = showAuthor;
|
||||
|
||||
if (m_event != nullptr && m_room->connection()->ignoredUsers().contains(m_event->senderId())) {
|
||||
if (m_event != nullptr) {
|
||||
if (showAuthor) {
|
||||
beginInsertRows({}, 0, 0);
|
||||
m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}});
|
||||
@@ -248,23 +193,10 @@ 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()->ignoredUsers().contains(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");
|
||||
}
|
||||
@@ -279,7 +211,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
if (!component.content.isEmpty()) {
|
||||
return component.content;
|
||||
}
|
||||
return EventHandler::richBody(m_room, m_event.get());
|
||||
return eventHandler.getRichBody();
|
||||
}
|
||||
if (role == ComponentTypeRole) {
|
||||
return component.type;
|
||||
@@ -288,7 +220,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return component.attributes;
|
||||
}
|
||||
if (role == EventIdRole) {
|
||||
return EventHandler::id(m_event.get());
|
||||
return eventHandler.getId();
|
||||
}
|
||||
if (role == TimeRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||
@@ -296,7 +228,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return EventHandler::time(m_event.get(), m_isPending, lastUpdated);
|
||||
return eventHandler.getTime(m_isPending, lastUpdated);
|
||||
}
|
||||
if (role == TimeStringRole) {
|
||||
const auto pendingIt = std::find_if(m_room->pendingEvents().cbegin(), m_room->pendingEvents().cend(), [this](const PendingEventItem &pendingEvent) {
|
||||
@@ -304,13 +236,13 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
});
|
||||
|
||||
auto lastUpdated = pendingIt == m_room->pendingEvents().cend() ? QDateTime() : pendingIt->lastUpdated();
|
||||
return EventHandler::timeString(m_event.get(), QStringLiteral("hh:mm"), m_isPending, lastUpdated);
|
||||
return eventHandler.getTimeString(false, QLocale::ShortFormat, m_isPending, lastUpdated);
|
||||
}
|
||||
if (role == AuthorRole) {
|
||||
return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
|
||||
return QVariant::fromValue(eventHandler.getAuthor(m_isPending));
|
||||
}
|
||||
if (role == MediaInfoRole) {
|
||||
return EventHandler::mediaInfo(m_room, m_event.get());
|
||||
return eventHandler.getMediaInfo();
|
||||
}
|
||||
if (role == FileTransferInfoRole) {
|
||||
return QVariant::fromValue(m_room->cachedFileTransferInfo(m_event.get()));
|
||||
@@ -319,22 +251,25 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
|
||||
return QVariant::fromValue<ItineraryModel *>(m_itineraryModel);
|
||||
}
|
||||
if (role == LatitudeRole) {
|
||||
return EventHandler::latitude(m_event.get());
|
||||
return eventHandler.getLatitude();
|
||||
}
|
||||
if (role == LongitudeRole) {
|
||||
return EventHandler::longitude(m_event.get());
|
||||
return eventHandler.getLongitude();
|
||||
}
|
||||
if (role == AssetRole) {
|
||||
return EventHandler::locationAssetType(m_event.get());
|
||||
return eventHandler.getLocationAssetType();
|
||||
}
|
||||
if (role == PollHandlerRole) {
|
||||
return QVariant::fromValue<PollHandler *>(m_room->poll(m_eventId));
|
||||
}
|
||||
if (role == IsReplyRole) {
|
||||
return eventHandler.hasReply();
|
||||
}
|
||||
if (role == ReplyEventIdRole) {
|
||||
return EventHandler::replyId(m_event.get());
|
||||
return eventHandler.getReplyId();
|
||||
}
|
||||
if (role == ReplyAuthorRole) {
|
||||
return QVariant::fromValue(EventHandler::replyAuthor(m_room, m_event.get()));
|
||||
return QVariant::fromValue(eventHandler.getReplyAuthor());
|
||||
}
|
||||
if (role == ReplyContentModelRole) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_replyModel);
|
||||
@@ -347,12 +282,6 @@ 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 {};
|
||||
}
|
||||
@@ -380,11 +309,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;
|
||||
}
|
||||
|
||||
@@ -393,12 +322,6 @@ void MessageContentModel::resetModel()
|
||||
beginResetModel();
|
||||
m_components.clear();
|
||||
|
||||
if ((m_event && m_room->connection()->ignoredUsers().contains(m_event->senderId())) || m_notFound) {
|
||||
m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
|
||||
endResetModel();
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_event == nullptr) {
|
||||
m_components += MessageComponent{MessageComponentType::Loading, QString(), {}};
|
||||
endResetModel();
|
||||
@@ -413,7 +336,7 @@ void MessageContentModel::resetModel()
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
void MessageContentModel::resetContent(bool isEditing)
|
||||
{
|
||||
Q_ASSERT(m_event != nullptr);
|
||||
|
||||
@@ -422,7 +345,7 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
m_components.remove(startRow, rowCount() - startRow);
|
||||
endRemoveRows();
|
||||
|
||||
const auto newComponents = messageContentComponents(isEditing, isThreading);
|
||||
const auto newComponents = messageContentComponents(isEditing);
|
||||
if (newComponents.size() == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -431,7 +354,7 @@ void MessageContentModel::resetContent(bool isEditing, bool isThreading)
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing, bool isThreading)
|
||||
QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEditing)
|
||||
{
|
||||
QList<MessageComponent> newComponents;
|
||||
|
||||
@@ -451,45 +374,35 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
|
||||
}
|
||||
|
||||
if (isEditing) {
|
||||
newComponents += MessageComponent{MessageComponentType::ChatBar, QString(), {}};
|
||||
newComponents += MessageComponent{MessageComponentType::Edit, QString(), {}};
|
||||
} else {
|
||||
newComponents.append(componentsForType(MessageComponentType::typeForEvent(*m_event.get())));
|
||||
EventHandler eventHandler(m_room, m_event.get());
|
||||
newComponents.append(componentsForType(eventHandler.messageComponentType()));
|
||||
}
|
||||
|
||||
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_isReply) {
|
||||
if (m_event == nullptr || m_replyModel != nullptr || m_isReply) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!EventHandler::hasReply(m_event.get()) || (EventHandler::isThreaded(m_event.get()) && NeoChatConfig::self()->threads())) {
|
||||
if (m_replyModel) {
|
||||
delete m_replyModel;
|
||||
}
|
||||
EventHandler eventHandler(m_room, m_event.get());
|
||||
if (!eventHandler.hasReply()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_replyModel != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto replyEvent = m_room->findInTimeline(EventHandler::replyId(m_event.get()));
|
||||
const auto replyEvent = m_room->findInTimeline(eventHandler.getReplyId());
|
||||
if (replyEvent == m_room->historyEdge()) {
|
||||
m_replyModel = new MessageContentModel(m_room, EventHandler::replyId(m_event.get()), true, false, this);
|
||||
m_replyModel = new MessageContentModel(m_room, eventHandler.getReplyId(), true);
|
||||
} else {
|
||||
m_replyModel = new MessageContentModel(m_room, replyEvent->get(), true, false, this);
|
||||
m_replyModel = new MessageContentModel(m_room, replyEvent->get(), true);
|
||||
}
|
||||
|
||||
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
|
||||
|
||||
@@ -6,12 +6,11 @@
|
||||
#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;
|
||||
@@ -65,21 +64,17 @@ 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 *parent = nullptr);
|
||||
MessageContentModel(NeoChatRoom *room, const QString &eventId, bool isReply = false, bool isPending = false, MessageContentModel *parent = nullptr);
|
||||
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);
|
||||
|
||||
bool showAuthor() const;
|
||||
void setShowAuthor(bool showAuthor);
|
||||
@@ -120,22 +115,18 @@ 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, bool isThreading = false);
|
||||
QList<MessageComponent> messageContentComponents(bool isEditing = false, bool isThreading = false);
|
||||
void resetContent(bool isEditing = false);
|
||||
QList<MessageComponent> messageContentComponents(bool isEditing = false);
|
||||
|
||||
QPointer<MessageContentModel> m_replyModel;
|
||||
void updateReplyModel();
|
||||
|
||||
@@ -22,8 +22,11 @@
|
||||
#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;
|
||||
@@ -66,11 +69,6 @@ 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
|
||||
@@ -88,16 +86,10 @@ 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();
|
||||
@@ -158,9 +150,8 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
refreshLastUserEvents(i);
|
||||
}
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this](Quotient::RoomEvent *event) {
|
||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] {
|
||||
m_initialized = true;
|
||||
createEventObjects(event);
|
||||
beginInsertRows({}, 0, 0);
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
||||
@@ -223,6 +214,22 @@ 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 {
|
||||
@@ -390,8 +397,6 @@ 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)) {
|
||||
@@ -432,30 +437,31 @@ 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::richBody(m_currentRoom, &evt);
|
||||
return eventHandler.getRichBody();
|
||||
}
|
||||
|
||||
if (role == ContentModelRole) {
|
||||
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() && !evt.id().isEmpty()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_currentRoom, &evt));
|
||||
}
|
||||
if (!modelId.isEmpty()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(m_contentModels.at(modelId).get());
|
||||
if (evt.isStateEvent()) {
|
||||
if (evt.matrixType() == QStringLiteral("org.matrix.msc3672.beacon_info")) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_currentRoom, &evt));
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == GenericDisplayRole) {
|
||||
return EventHandler::genericBody(&evt);
|
||||
return eventHandler.getGenericBody();
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
@@ -463,22 +469,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == AuthorRole) {
|
||||
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());
|
||||
return QVariant::fromValue(eventHandler.getAuthor(isPending));
|
||||
}
|
||||
|
||||
if (role == HighlightRole) {
|
||||
return EventHandler::isHighlighted(m_currentRoom, &evt);
|
||||
return eventHandler.isHighlighted();
|
||||
}
|
||||
|
||||
if (role == SpecialMarksRole) {
|
||||
@@ -491,11 +486,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return pendingIt->deliveryStatus();
|
||||
}
|
||||
|
||||
if (EventHandler::isHidden(m_currentRoom, &evt)) {
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
|
||||
if (EventHandler::isThreaded(&evt) && EventHandler::threadRoot(&evt) != EventHandler::id(&evt) && NeoChatConfig::threads()) {
|
||||
if (eventHandler.isHidden()) {
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
|
||||
@@ -503,7 +494,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == EventIdRole) {
|
||||
return EventHandler::id(&evt);
|
||||
return eventHandler.getId();
|
||||
}
|
||||
|
||||
if (role == ProgressInfoRole) {
|
||||
@@ -519,20 +510,20 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (role == TimeRole) {
|
||||
auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
|
||||
return EventHandler::time(&evt, isPending, lastUpdated);
|
||||
return eventHandler.getTime(isPending, lastUpdated);
|
||||
}
|
||||
|
||||
if (role == SectionRole) {
|
||||
auto lastUpdated = isPending ? pendingIt->lastUpdated() : QDateTime();
|
||||
return EventHandler::timeString(&evt, true, QLocale::ShortFormat, isPending, lastUpdated);
|
||||
return eventHandler.getTimeString(true, QLocale::ShortFormat, isPending, lastUpdated);
|
||||
}
|
||||
|
||||
if (role == IsThreadedRole) {
|
||||
return EventHandler::isThreaded(&evt);
|
||||
return eventHandler.isThreaded();
|
||||
}
|
||||
|
||||
if (role == ThreadRootRole) {
|
||||
return EventHandler::threadRoot(&evt);
|
||||
return eventHandler.threadRoot();
|
||||
}
|
||||
|
||||
if (role == ShowSectionRole) {
|
||||
@@ -585,7 +576,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == AuthorDisplayNameRole) {
|
||||
return EventHandler::authorDisplayName(m_currentRoom, &evt, isPending);
|
||||
return eventHandler.getAuthorDisplayName(isPending);
|
||||
}
|
||||
|
||||
if (role == IsRedactedRole) {
|
||||
@@ -597,11 +588,11 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == MediaInfoRole) {
|
||||
return EventHandler::mediaInfo(m_currentRoom, &evt);
|
||||
return eventHandler.getMediaInfo();
|
||||
}
|
||||
|
||||
if (role == IsEditableRole) {
|
||||
return MessageComponentType::typeForEvent(evt) == MessageComponentType::Text && evt.senderId() == m_currentRoom->localMember().id();
|
||||
return eventHandler.messageComponentType() == MessageComponentType::Text && evt.senderId() == m_currentRoom->localMember().id();
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -628,29 +619,6 @@ 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.
|
||||
@@ -711,9 +679,4 @@ 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,12 +8,9 @@
|
||||
#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;
|
||||
|
||||
@@ -56,8 +53,8 @@ public:
|
||||
|
||||
ContentModelRole, /**< The MessageContentModel for the event. */
|
||||
|
||||
IsThreadedRole, /**< Whether the message is in a thread. */
|
||||
ThreadRootRole, /**< The Matrix ID of the thread root message, if any . */
|
||||
IsThreadedRole,
|
||||
ThreadRootRole,
|
||||
|
||||
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||
|
||||
@@ -106,8 +103,6 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const;
|
||||
|
||||
Q_INVOKABLE ThreadModel *threadModelForRootId(const QString &threadRootId) const;
|
||||
|
||||
protected:
|
||||
bool event(QEvent *event) override;
|
||||
|
||||
@@ -120,10 +115,7 @@ 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 "neochatroommember.h"
|
||||
#include "timelinemodel.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
|
||||
@@ -121,11 +121,12 @@ 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::plainBody(dynamic_cast<NeoChatRoom *>(room), roomEvent, true),
|
||||
+ eventHandler.getPlainBody(true),
|
||||
.authorName = room->member(authorId).htmlSafeDisplayName(),
|
||||
.authorAvatar = authorAvatar,
|
||||
.eventId = roomEvent->id(),
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatconnection.h"
|
||||
|
||||
#include <KLazyLocalizedString>
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "reactionmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QFont>
|
||||
@@ -14,6 +15,8 @@
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <Quotient/roommember.h>
|
||||
|
||||
ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
|
||||
: QAbstractListModel(nullptr)
|
||||
, m_room(room)
|
||||
|
||||
@@ -245,7 +245,8 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
|
||||
return QString();
|
||||
}
|
||||
return EventHandler::subtitleText(room, room->lastEvent());
|
||||
EventHandler eventHandler(room, room->lastEvent());
|
||||
return eventHandler.subtitleText();
|
||||
}
|
||||
if (role == AvatarImageRole) {
|
||||
return room->avatar(128);
|
||||
|
||||
@@ -357,7 +357,8 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
|
||||
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
|
||||
return QString();
|
||||
}
|
||||
return EventHandler::subtitleText(room, room->lastEvent());
|
||||
EventHandler eventHandler(room, room->lastEvent());
|
||||
return eventHandler.subtitleText();
|
||||
}
|
||||
if (role == AvatarImageRole) {
|
||||
return room->avatar(128);
|
||||
|
||||
@@ -66,17 +66,7 @@ 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;
|
||||
@@ -89,16 +79,18 @@ 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<NeochatRoomMember *>(m_memberObjects.at(event.senderId()).get());
|
||||
return QVariant::fromValue(eventHandler.getAuthor());
|
||||
case ShowSectionRole:
|
||||
if (row == 0) {
|
||||
return true;
|
||||
}
|
||||
return event.originTimestamp().date() != m_result->results[row - 1].result->originTimestamp().date();
|
||||
case SectionRole:
|
||||
return EventHandler::timeString(&event, true);
|
||||
return eventHandler.getTimeString(true);
|
||||
case ShowReactionsRole:
|
||||
return false;
|
||||
case ShowReadMarkersRole:
|
||||
@@ -106,13 +98,13 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
case IsPendingRole:
|
||||
return false;
|
||||
case HighlightRole:
|
||||
return EventHandler::isHighlighted(m_room, &event);
|
||||
return eventHandler.isHighlighted();
|
||||
case EventIdRole:
|
||||
return EventHandler::id(&event);
|
||||
return eventHandler.getId();
|
||||
case IsThreadedRole:
|
||||
return EventHandler::isThreaded(&event);
|
||||
return eventHandler.isThreaded();
|
||||
case ThreadRootRole:
|
||||
return EventHandler::threadRoot(&event);
|
||||
return eventHandler.threadRoot();
|
||||
case ContentModelRole: {
|
||||
if (!event.isStateEvent()) {
|
||||
return QVariant::fromValue<MessageContentModel *>(new MessageContentModel(m_room, &event));
|
||||
|
||||
@@ -9,8 +9,6 @@
|
||||
|
||||
#include <Quotient/csapi/search.h>
|
||||
|
||||
#include "neochatroommember.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
@@ -125,6 +123,4 @@ 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,6 +7,7 @@
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
|
||||
: QAbstractItemModel(parent)
|
||||
|
||||
@@ -1,190 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "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"
|
||||
@@ -1,142 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <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
|
||||
QML_ELEMENT
|
||||
|
||||
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,15 +3,11 @@
|
||||
|
||||
#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);
|
||||
@@ -27,7 +23,6 @@ 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);
|
||||
}
|
||||
|
||||
@@ -41,69 +36,6 @@ 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->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)
|
||||
{
|
||||
@@ -146,11 +78,7 @@ QVariant TimelineEndModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == DelegateTypeRole) {
|
||||
if (idx.row() == 1 || rowCount() == 1) {
|
||||
return m_room->allHistoryLoaded() ? DelegateType::TimelineEnd : DelegateType::Loading;
|
||||
} else {
|
||||
return DelegateType::Predecessor;
|
||||
}
|
||||
return m_room->allHistoryLoaded() ? DelegateType::TimelineEnd : DelegateType::Loading;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -158,10 +86,7 @@ QVariant TimelineEndModel::data(const QModelIndex &idx, int role) const
|
||||
int TimelineEndModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
if (m_room == nullptr) {
|
||||
return 0;
|
||||
}
|
||||
return m_room->predecessorId().isEmpty() ? 1 : (m_room->allHistoryLoaded() ? 2 : 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> TimelineEndModel::roleNames() const
|
||||
|
||||
@@ -10,57 +10,6 @@
|
||||
#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
|
||||
*
|
||||
@@ -159,6 +108,5 @@ Q_SIGNALS:
|
||||
|
||||
private:
|
||||
MessageEventModel *m_messageEventModel = nullptr;
|
||||
TimelineBeginningModel *m_timelineBeginningModel = nullptr;
|
||||
TimelineEndModel *m_timelineEndModel = nullptr;
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
#include <Quotient/avatar.h>
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
#include <Quotient/room.h>
|
||||
|
||||
#include "enums/powerlevel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
@@ -16,7 +16,6 @@ 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,7 +58,6 @@ 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
|
||||
@@ -104,7 +102,6 @@ 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
|
||||
@@ -145,7 +142,6 @@ 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
|
||||
@@ -190,7 +186,6 @@ 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
|
||||
@@ -230,7 +225,6 @@ 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
|
||||
@@ -261,7 +255,7 @@ Action=Popup
|
||||
Name=Share
|
||||
Name[ar]=شارك
|
||||
Name[ca]=Compartició
|
||||
Name[ca@valencia]=Compartició
|
||||
Name[ca@valencia]=Compartiu
|
||||
Name[cs]=Sdílet
|
||||
Name[en_GB]=Share
|
||||
Name[eo]=Kundividi
|
||||
@@ -269,8 +263,6 @@ 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
|
||||
@@ -279,7 +271,6 @@ 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]=பகிர்
|
||||
@@ -297,8 +288,6 @@ 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
|
||||
@@ -307,7 +296,6 @@ 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]=எதையோ பகிர்ந்ததன் விளைவு
|
||||
|
||||
@@ -89,10 +89,6 @@
|
||||
<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>
|
||||
|
||||
9
src/neochatconfig.kcfgc
Normal file
9
src/neochatconfig.kcfgc
Normal file
@@ -0,0 +1,9 @@
|
||||
# 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,12 +9,14 @@
|
||||
#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>
|
||||
@@ -44,7 +46,6 @@ NeoChatConnection::NeoChatConnection(QObject *parent)
|
||||
: Connection(parent)
|
||||
, m_threePIdModel(new ThreePIdModel(this))
|
||||
{
|
||||
m_linkPreviewers.setMaxCost(20);
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
@@ -52,7 +53,6 @@ NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
||||
: Connection(server, parent)
|
||||
, m_threePIdModel(new ThreePIdModel(this))
|
||||
{
|
||||
m_linkPreviewers.setMaxCost(20);
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
@@ -564,31 +564,14 @@ LinkPreviewer *NeoChatConnection::previewerForLink(const QUrl &link)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto previewer = m_linkPreviewers.object(link);
|
||||
auto previewer = m_linkPreviewers.value(link, nullptr);
|
||||
if (previewer != nullptr) {
|
||||
return previewer;
|
||||
}
|
||||
|
||||
previewer = new LinkPreviewer(link, this);
|
||||
m_linkPreviewers.insert(link, previewer);
|
||||
m_linkPreviewers[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"
|
||||
|
||||
@@ -3,20 +3,16 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QCache>
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <QCoroTask>
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
#include <Quotient/keyimport.h>
|
||||
#endif
|
||||
|
||||
#include "linkpreviewer.h"
|
||||
#include "models/threepidmodel.h"
|
||||
|
||||
class LinkPreviewer;
|
||||
|
||||
class NeoChatConnection : public Quotient::Connection
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -173,10 +169,6 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE QString accountDataJsonString(const QString &type) const;
|
||||
|
||||
#if Quotient_VERSION_MINOR > 8
|
||||
Q_INVOKABLE Quotient::KeyImport::Error exportMegolmSessions(const QString &passphrase, const QString &path);
|
||||
#endif
|
||||
|
||||
qsizetype directChatNotifications() const;
|
||||
bool directChatsHaveHighlightNotifications() const;
|
||||
qsizetype homeNotifications() const;
|
||||
@@ -222,7 +214,7 @@ private:
|
||||
|
||||
int m_badgeNotificationCount = 0;
|
||||
|
||||
QCache<QUrl, LinkPreviewer> m_linkPreviewers;
|
||||
QHash<QUrl, LinkPreviewer *> m_linkPreviewers;
|
||||
|
||||
bool m_canCheckMutualRooms = false;
|
||||
};
|
||||
|
||||
@@ -64,7 +64,6 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
{
|
||||
m_mainCache = new ChatBarCache(this);
|
||||
m_editCache = new ChatBarCache(this);
|
||||
m_threadCache = new ChatBarCache(this);
|
||||
|
||||
connect(connection, &Connection::accountDataChanged, this, &NeoChatRoom::updatePushNotificationState);
|
||||
connect(this, &Room::fileTransferCompleted, this, [this] {
|
||||
@@ -303,16 +302,10 @@ void NeoChatRoom::acceptInvitation()
|
||||
|
||||
void NeoChatRoom::forget()
|
||||
{
|
||||
QStringList roomIds{id()};
|
||||
|
||||
NeoChatRoom *predecessor = this;
|
||||
while (predecessor = dynamic_cast<NeoChatRoom *>(predecessor->predecessor(JoinState::Join)), predecessor && !roomIds.contains(predecessor->id())) {
|
||||
roomIds += predecessor->id();
|
||||
}
|
||||
|
||||
for (const auto &id : roomIds) {
|
||||
connection()->forgetRoom(id);
|
||||
if (const auto &predecessor = dynamic_cast<NeoChatRoom *>(this->predecessor(JoinState::Join))) {
|
||||
predecessor->forget();
|
||||
}
|
||||
connection()->forgetRoom(id());
|
||||
}
|
||||
|
||||
void NeoChatRoom::sendTypingNotification(bool isTyping)
|
||||
@@ -523,10 +516,9 @@ void NeoChatRoom::postMessage(const QString &rawText,
|
||||
MessageEventType type,
|
||||
const QString &replyEventId,
|
||||
const QString &relateToEventId,
|
||||
const QString &threadRootId,
|
||||
const QString &fallbackId)
|
||||
const QString &threadRootId)
|
||||
{
|
||||
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId, threadRootId, fallbackId);
|
||||
postHtmlMessage(rawText, text, type, replyEventId, relateToEventId, threadRootId);
|
||||
}
|
||||
|
||||
void NeoChatRoom::postHtmlMessage(const QString &text,
|
||||
@@ -534,8 +526,7 @@ void NeoChatRoom::postHtmlMessage(const QString &text,
|
||||
MessageEventType type,
|
||||
const QString &replyEventId,
|
||||
const QString &relateToEventId,
|
||||
const QString &threadRootId,
|
||||
const QString &fallbackId)
|
||||
const QString &threadRootId)
|
||||
{
|
||||
bool isReply = !replyEventId.isEmpty();
|
||||
bool isEdit = !relateToEventId.isEmpty();
|
||||
@@ -546,19 +537,9 @@ void NeoChatRoom::postHtmlMessage(const QString &text,
|
||||
}
|
||||
|
||||
if (isThread) {
|
||||
bool isFallingBack = !fallbackId.isEmpty();
|
||||
QString replyEventId = isFallingBack ? fallbackId : QString();
|
||||
if (isReply) {
|
||||
isFallingBack = false;
|
||||
replyEventId = EventHandler::id(replyIt->get());
|
||||
}
|
||||
EventHandler eventHandler(this, &**replyIt);
|
||||
|
||||
// If we are not replying and there is no fallback ID it means a new thread
|
||||
// is being created.
|
||||
if (!isFallingBack && !isReply) {
|
||||
isFallingBack = true;
|
||||
replyEventId = threadRootId;
|
||||
}
|
||||
const bool isFallingBack = !eventHandler.isThreaded();
|
||||
|
||||
// clang-format off
|
||||
QJsonObject json{
|
||||
@@ -603,10 +584,12 @@ void NeoChatRoom::postHtmlMessage(const QString &text,
|
||||
if (isReply) {
|
||||
const auto &replyEvt = **replyIt;
|
||||
|
||||
EventHandler eventHandler(this, &**replyIt);
|
||||
|
||||
// clang-format off
|
||||
QJsonObject json{
|
||||
{"msgtype"_ls, msgTypeToString(type)},
|
||||
{"body"_ls, "> <%1> %2\n\n%3"_ls.arg(replyEvt.senderId(), EventHandler::plainBody(this, &replyEvt), text)},
|
||||
{"body"_ls, "> <%1> %2\n\n%3"_ls.arg(replyEvt.senderId(), eventHandler.getPlainBody(), text)},
|
||||
{"format"_ls, "org.matrix.custom.html"_ls},
|
||||
{"m.relates_to"_ls,
|
||||
QJsonObject {
|
||||
@@ -618,7 +601,7 @@ void NeoChatRoom::postHtmlMessage(const QString &text,
|
||||
}
|
||||
},
|
||||
{"formatted_body"_ls,
|
||||
"<mx-reply><blockquote><a href=\"https://matrix.to/#/%1/%2\">In reply to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br>%5</blockquote></mx-reply>%6"_ls.arg(id(), replyEventId, replyEvt.senderId(), replyEvt.senderId(), EventHandler::richBody(this, &replyEvt), html)
|
||||
"<mx-reply><blockquote><a href=\"https://matrix.to/#/%1/%2\">In reply to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br>%5</blockquote></mx-reply>%6"_ls.arg(id(), replyEventId, replyEvt.senderId(), replyEvt.senderId(), eventHandler.getRichBody(), html)
|
||||
}
|
||||
};
|
||||
// clang-format on
|
||||
@@ -1549,11 +1532,6 @@ ChatBarCache *NeoChatRoom::editCache() const
|
||||
return m_editCache;
|
||||
}
|
||||
|
||||
ChatBarCache *NeoChatRoom::threadCache() const
|
||||
{
|
||||
return m_threadCache;
|
||||
}
|
||||
|
||||
void NeoChatRoom::replyLastMessage()
|
||||
{
|
||||
const auto &timelineBottom = messageEvents().rbegin();
|
||||
@@ -1770,7 +1748,6 @@ void NeoChatRoom::downloadEventFromServer(const QString &eventId)
|
||||
connect(job, &BaseJob::success, this, [this, job, eventId] {
|
||||
// The event may have arrived in the meantime so check it's not in the timeline.
|
||||
if (findInTimeline(eventId) != historyEdge()) {
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1778,11 +1755,6 @@ void NeoChatRoom::downloadEventFromServer(const QString &eventId)
|
||||
m_extraEvents.push_back(std::move(event));
|
||||
Q_EMIT extraEventLoaded(eventId);
|
||||
});
|
||||
connect(job, &BaseJob::failure, this, [this, job, eventId] {
|
||||
if (job->error() == BaseJob::NotFound) {
|
||||
Q_EMIT extraEventNotFound(eventId);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
const RoomEvent *NeoChatRoom::getEvent(const QString &eventId) const
|
||||
@@ -1795,11 +1767,6 @@ const RoomEvent *NeoChatRoom::getEvent(const QString &eventId) const
|
||||
return timelineIt->get();
|
||||
}
|
||||
|
||||
const auto pendingIt = findPendingEvent(eventId);
|
||||
if (pendingIt != pendingEvents().end()) {
|
||||
return pendingIt->event();
|
||||
}
|
||||
|
||||
auto extraIt = std::find_if(m_extraEvents.begin(), m_extraEvents.end(), [eventId](const Quotient::event_ptr_tt<Quotient::RoomEvent> &event) {
|
||||
return event->id() == eventId;
|
||||
});
|
||||
|
||||
@@ -201,11 +201,6 @@ class NeoChatRoom : public Quotient::Room
|
||||
*/
|
||||
Q_PROPERTY(ChatBarCache *editCache READ editCache CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The cache for the thread chat bar in the room.
|
||||
*/
|
||||
Q_PROPERTY(ChatBarCache *threadCache READ threadCache CONSTANT)
|
||||
|
||||
#if Quotient_VERSION_MINOR == 8
|
||||
Q_PROPERTY(QList<Quotient::RoomMember> otherMembersTyping READ otherMembersTyping NOTIFY typingChanged)
|
||||
#endif
|
||||
@@ -516,8 +511,6 @@ public:
|
||||
|
||||
ChatBarCache *editCache() const;
|
||||
|
||||
ChatBarCache *threadCache() const;
|
||||
|
||||
/**
|
||||
* @brief Reply to the last message sent in the timeline.
|
||||
*
|
||||
@@ -616,7 +609,6 @@ private:
|
||||
|
||||
ChatBarCache *m_mainCache;
|
||||
ChatBarCache *m_editCache;
|
||||
ChatBarCache *m_threadCache;
|
||||
|
||||
QCache<QString, PollHandler> m_polls;
|
||||
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
|
||||
@@ -655,7 +647,6 @@ Q_SIGNALS:
|
||||
void urlPreviewEnabledChanged();
|
||||
void maxRoomVersionChanged();
|
||||
void extraEventLoaded(const QString &eventId);
|
||||
void extraEventNotFound(const QString &eventId);
|
||||
|
||||
public Q_SLOTS:
|
||||
/**
|
||||
@@ -700,8 +691,7 @@ public Q_SLOTS:
|
||||
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
||||
const QString &replyEventId = QString(),
|
||||
const QString &relateToEventId = QString(),
|
||||
const QString &threadRootId = QString(),
|
||||
const QString &fallbackId = QString());
|
||||
const QString &threadRootId = QString());
|
||||
|
||||
/**
|
||||
* @brief Send an html message to the room.
|
||||
@@ -717,8 +707,7 @@ public Q_SLOTS:
|
||||
Quotient::MessageEventType type = Quotient::MessageEventType::Text,
|
||||
const QString &replyEventId = QString(),
|
||||
const QString &relateToEventId = QString(),
|
||||
const QString &threadRootId = QString(),
|
||||
const QString &fallbackId = QString());
|
||||
const QString &threadRootId = QString());
|
||||
|
||||
/**
|
||||
* @brief Set the room avatar.
|
||||
|
||||
@@ -1,166 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "neochatroommember.h"
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
NeochatRoomMember::NeochatRoomMember(NeoChatRoom *room, const QString &memberId)
|
||||
: m_room(room)
|
||||
, m_memberId(memberId)
|
||||
{
|
||||
Q_ASSERT(!m_memberId.isEmpty());
|
||||
|
||||
if (m_room != nullptr) {
|
||||
connect(m_room, &NeoChatRoom::memberNameUpdated, this, [this](Quotient::RoomMember member) {
|
||||
if (member.id() == m_memberId) {
|
||||
Q_EMIT displayNameUpdated();
|
||||
}
|
||||
});
|
||||
connect(m_room, &NeoChatRoom::memberAvatarUpdated, this, [this](Quotient::RoomMember member) {
|
||||
if (member.id() == m_memberId) {
|
||||
Q_EMIT avatarUpdated();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::id() const
|
||||
{
|
||||
return m_memberId;
|
||||
}
|
||||
|
||||
Quotient::Uri NeochatRoomMember::uri() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).uri();
|
||||
}
|
||||
|
||||
bool NeochatRoomMember::isLocalMember() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).isLocalMember();
|
||||
}
|
||||
|
||||
Quotient::Membership NeochatRoomMember::membershipState() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return Quotient::Membership::Leave;
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).membershipState();
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::name() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).name();
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::displayName() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).displayName();
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeDisplayName() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).htmlSafeDisplayName();
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::fullName() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).fullName();
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeFullName() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).htmlSafeFullName();
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::disambiguatedName() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).disambiguatedName();
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::htmlSafeDisambiguatedName() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return id();
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).htmlSafeDisambiguatedName();
|
||||
}
|
||||
|
||||
int NeochatRoomMember::hue() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).hue();
|
||||
}
|
||||
|
||||
qreal NeochatRoomMember::hueF() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return 0.0;
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).hueF();
|
||||
}
|
||||
|
||||
QColor NeochatRoomMember::color() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).color();
|
||||
}
|
||||
|
||||
QString NeochatRoomMember::avatarMediaId() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).avatarMediaId();
|
||||
}
|
||||
|
||||
QUrl NeochatRoomMember::avatarUrl() const
|
||||
{
|
||||
if (m_room == nullptr || m_memberId.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return m_room->member(m_memberId).avatarUrl();
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
#include <qqmlintegration.h>
|
||||
|
||||
#include <Quotient/roommember.h>
|
||||
#include <Quotient/uri.h>
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class NeochatRoomMember
|
||||
*
|
||||
* This class is a shim around RoomMember that can be safety passed to QML.
|
||||
*
|
||||
* Because RoomMember has an internal pointer to a RoomMemberEvent it is
|
||||
* designed to be created used then quickly discarded as the stste event is changed
|
||||
* every time the member updates. Passing these to QML which will hold onto them
|
||||
* can lead to accessing an already deleted Quotient::RoomMemberEvent relatively easily.
|
||||
*
|
||||
* This class instead holds a member ID and can therefore always safely create and
|
||||
* access Quotient::RoomMember objects while being used as long as needed by QML.
|
||||
*
|
||||
* @note This is only needed to pass to QML if only accessing in CPP RoomMmeber can
|
||||
* be used safely.
|
||||
*
|
||||
* @note The interface is the same as Quotient::RoomMember.
|
||||
*
|
||||
* @sa Quotient::RoomMember
|
||||
*/
|
||||
class NeochatRoomMember : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_UNCREATABLE("")
|
||||
|
||||
Q_PROPERTY(QString id READ id CONSTANT)
|
||||
Q_PROPERTY(Quotient::Uri uri READ uri CONSTANT)
|
||||
Q_PROPERTY(bool isLocalMember READ isLocalMember CONSTANT)
|
||||
Q_PROPERTY(QString displayName READ displayName NOTIFY displayNameUpdated)
|
||||
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameUpdated)
|
||||
Q_PROPERTY(QString fullName READ fullName NOTIFY displayNameUpdated)
|
||||
Q_PROPERTY(QString htmlSafeFullName READ htmlSafeFullName NOTIFY displayNameUpdated)
|
||||
Q_PROPERTY(QString disambiguatedName READ disambiguatedName NOTIFY displayNameUpdated)
|
||||
Q_PROPERTY(QString htmlSafeDisambiguatedName READ htmlSafeDisambiguatedName NOTIFY displayNameUpdated)
|
||||
Q_PROPERTY(int hue READ hue CONSTANT)
|
||||
Q_PROPERTY(qreal hueF READ hueF CONSTANT)
|
||||
Q_PROPERTY(QColor color READ color CONSTANT)
|
||||
Q_PROPERTY(QUrl avatarUrl READ avatarUrl NOTIFY avatarUpdated)
|
||||
|
||||
public:
|
||||
NeochatRoomMember() = default;
|
||||
explicit NeochatRoomMember(NeoChatRoom *room, const QString &memberId);
|
||||
|
||||
QString id() const;
|
||||
Quotient::Uri uri() const;
|
||||
bool isLocalMember() const;
|
||||
Quotient::Membership membershipState() const;
|
||||
QString name() const;
|
||||
QString displayName() const;
|
||||
QString htmlSafeDisplayName() const;
|
||||
QString fullName() const;
|
||||
QString htmlSafeFullName() const;
|
||||
QString disambiguatedName() const;
|
||||
QString htmlSafeDisambiguatedName() const;
|
||||
int hue() const;
|
||||
qreal hueF() const;
|
||||
QColor color() const;
|
||||
QString avatarMediaId() const;
|
||||
QUrl avatarUrl() const;
|
||||
|
||||
Q_SIGNALS:
|
||||
void displayNameUpdated();
|
||||
void avatarUpdated();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
const QString m_memberId = QString();
|
||||
};
|
||||
@@ -17,7 +17,6 @@ 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,7 +57,6 @@ Comment[eu]=Bilatu gelak NeoChat-en
|
||||
Comment[fi]=Etsi huoneita NeoChatissä
|
||||
Comment[fr]=Trouver des salons dans NeoChat
|
||||
Comment[gl]=Atopa salas en NeoChat.
|
||||
Comment[he]=איתור חדרים ב־NeoChat
|
||||
Comment[hu]=Szobák keresése a NeoChatben
|
||||
Comment[ia]=Trova salas in NeoChat
|
||||
Comment[id]=Cari ruangan di NeoChat
|
||||
|
||||
@@ -4,34 +4,8 @@
|
||||
{
|
||||
"Name": "Tobias Fella",
|
||||
"Name[ar]": "توبياس فلة",
|
||||
"Name[ca@valencia]": "Tobias Fella",
|
||||
"Name[ca]": "Tobias Fella",
|
||||
"Name[cs]": "Tobias Fella",
|
||||
"Name[de]": "Tobias Fella",
|
||||
"Name[en_GB]": "Tobias Fella",
|
||||
"Name[eo]": "Tobias Fella",
|
||||
"Name[es]": "Tobias Fella",
|
||||
"Name[eu]": "Tobias Fella",
|
||||
"Name[fi]": "Tobias Fella",
|
||||
"Name[fr]": "Tobias Fella",
|
||||
"Name[gl]": "Tobias Fella",
|
||||
"Name[he]": "טוביאס פלה",
|
||||
"Name[hu]": "Tobias Fella",
|
||||
"Name[ia]": "Tobias Fella",
|
||||
"Name[it]": "Tobias Fella",
|
||||
"Name[ka]": "Tobias Fella",
|
||||
"Name[lv]": "Tobias Fella",
|
||||
"Name[nl]": "Tobias Fella",
|
||||
"Name[nn]": "Tobias Fella",
|
||||
"Name[pl]": "Tobias Fella",
|
||||
"Name[ru]": "Tobias Fella",
|
||||
"Name[sl]": "Tobias Fella",
|
||||
"Name[sv]": "Tobias Fella",
|
||||
"Name[ta]": "டோபியாஸ் ஃபெல்லா",
|
||||
"Name[tr]": "Tobias Fella",
|
||||
"Name[uk]": "Tobias Fella",
|
||||
"Name[x-test]": "xxTobias Fellaxx",
|
||||
"Name[zh_TW]": "Tobias Fella"
|
||||
"Name[x-test]": "xxTobias Fellaxx"
|
||||
}
|
||||
],
|
||||
"Category": "Utilities",
|
||||
@@ -40,14 +14,12 @@
|
||||
"Description[ca@valencia]": "Compartix a través de NeoChat",
|
||||
"Description[ca]": "Comparteix a través del NeoChat",
|
||||
"Description[de]": "Über NeoChat teilen",
|
||||
"Description[en_GB]": "Share via NeoChat",
|
||||
"Description[eo]": "Kundividi per NeoChat",
|
||||
"Description[es]": "Compartir mediante NeoChat",
|
||||
"Description[eu]": "Partekatu NeoChat bidez",
|
||||
"Description[fi]": "Jaa NeoChatillä",
|
||||
"Description[fr]": "Partager grâce à NeoChat",
|
||||
"Description[gl]": "Compartir por NeoChat",
|
||||
"Description[he]": "שיתוף דרך NeoChat",
|
||||
"Description[hu]": "Megosztás NeoChatben",
|
||||
"Description[ia]": "Comparti via NeoChat",
|
||||
"Description[it]": "Condividi tramite NeoChat",
|
||||
@@ -68,35 +40,9 @@
|
||||
"License": "GPL",
|
||||
"Name": "NeoChat",
|
||||
"Name[ar]": "نيوتشات",
|
||||
"Name[ast]": "NeoChat",
|
||||
"Name[ca@valencia]": "NeoChat",
|
||||
"Name[ca]": "NeoChat",
|
||||
"Name[cs]": "NeoChat",
|
||||
"Name[de]": "NeoChat",
|
||||
"Name[en_GB]": "NeoChat",
|
||||
"Name[eo]": "NeoChat",
|
||||
"Name[es]": "NeoChat",
|
||||
"Name[eu]": "NeoChat",
|
||||
"Name[fi]": "NeoChat",
|
||||
"Name[fr]": "NeoChat",
|
||||
"Name[gl]": "NeoChat",
|
||||
"Name[he]": "NeoChat",
|
||||
"Name[hu]": "NeoChat",
|
||||
"Name[ia]": "Neochat",
|
||||
"Name[it]": "NeoChat",
|
||||
"Name[ka]": "NeoChat",
|
||||
"Name[lv]": "NeoChat",
|
||||
"Name[nl]": "NeoChat",
|
||||
"Name[nn]": "NeoChat",
|
||||
"Name[pl]": "NeoChat",
|
||||
"Name[ru]": "NeoChat",
|
||||
"Name[sl]": "NeoChat",
|
||||
"Name[sv]": "NeoChat",
|
||||
"Name[ta]": "நியோச்சாட்",
|
||||
"Name[tr]": "NeoChat",
|
||||
"Name[uk]": "NeoChat",
|
||||
"Name[x-test]": "xxNeoChatxx",
|
||||
"Name[zh_TW]": "NeoChat",
|
||||
"X-Purpose-ActionDisplay": "NeoChat"
|
||||
},
|
||||
"X-Purpose-PluginTypes": [
|
||||
|
||||
@@ -19,22 +19,6 @@ QQC2.Menu {
|
||||
|
||||
margins: Kirigami.Units.smallSpacing
|
||||
|
||||
QQC2.MenuItem {
|
||||
text: i18nc("@action:button", "Show QR code")
|
||||
icon.name: "view-barcode-qr-symbolic"
|
||||
onTriggered: {
|
||||
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||
text: "https://matrix.to/#/" + root.connection.localUser.id,
|
||||
title: root.connection.localUser.displayName,
|
||||
subtitle: root.connection.localUser.id,
|
||||
avatarSource: root.connection.makeMediaUrl(root.connection.localUser.avatarUrl)
|
||||
});
|
||||
if (typeof root.closeDialog === "function") {
|
||||
root.closeDialog();
|
||||
}
|
||||
qrMax.open();
|
||||
}
|
||||
}
|
||||
QQC2.MenuItem {
|
||||
text: i18n("Edit this account")
|
||||
icon.name: "document-edit"
|
||||
@@ -61,7 +45,7 @@ QQC2.Menu {
|
||||
QQC2.MenuItem {
|
||||
text: i18n("Open developer tools")
|
||||
icon.name: "tools"
|
||||
visible: NeoChatConfig.developerTools
|
||||
visible: Config.developerTools
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
@@ -73,7 +57,7 @@ QQC2.Menu {
|
||||
QQC2.MenuItem {
|
||||
text: i18nc("@action:inmenu", "Open Secret Backup")
|
||||
icon.name: "unlock"
|
||||
visible: NeoChatConfig.secretBackup
|
||||
visible: Config.secretBackup
|
||||
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UnlockSSSSDialog'), {}, {
|
||||
title: i18nc("@title:window", "Open Key Backup")
|
||||
})
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user