Compare commits

..

4 Commits

Author SHA1 Message Date
l10n daemon script
32bea56a6d GIT_SILENT Sync po/docbooks with svn 2025-03-13 03:13:58 +00:00
l10n daemon script
661cf22667 GIT_SILENT Sync po/docbooks with svn 2025-03-10 03:49:36 +00:00
l10n daemon script
ad1254fb71 GIT_SILENT Sync po/docbooks with svn 2025-03-09 03:11:01 +00:00
Albert Astals Cid
7514a8a6f7 GIT_SILENT Upgrade release service version to 25.03.80. 2025-03-08 18:43:32 +01:00
447 changed files with 153707 additions and 171170 deletions

View File

@@ -170,24 +170,6 @@
} }
] ]
}, },
{
"name": "kunifiedpush",
"buildsystem": "cmake-ninja",
"builddir": true,
"sources": [
{
"type": "archive",
"url": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-1.0.0.tar.xz",
"sha256": "2ddeba21306d0307114ec50a2c38159ec62359f9fc6cdd58da30a369fbd550cf",
"x-checker-data": {
"type": "anitya",
"project-id": 375055,
"stable-only": true,
"url-template": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-$version.tar.xz"
}
}
]
},
{ {
"name": "neochat", "name": "neochat",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",

View File

@@ -12,7 +12,7 @@ include:
- /gitlab-templates/linux-qt6.yml - /gitlab-templates/linux-qt6.yml
- /gitlab-templates/linux-qt6-next.yml - /gitlab-templates/linux-qt6-next.yml
- /gitlab-templates/windows-qt6.yml - /gitlab-templates/windows-qt6.yml
- /gitlab-templates/freebsd-qt6.yml # - /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml - /gitlab-templates/flatpak.yml
- /gitlab-templates/snap-snapcraft-lxd.yml - /gitlab-templates/snap-snapcraft-lxd.yml
- /gitlab-templates/craft-android-qt6-apks.yml - /gitlab-templates/craft-android-qt6-apks.yml

View File

@@ -34,7 +34,6 @@ Dependencies:
'require': 'require':
'frameworks/kdbusaddons': '@latest-kf6' 'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/purpose': '@latest-kf6' 'frameworks/purpose': '@latest-kf6'
'libraries/kunifiedpush': '@latest-kf6'
- 'on': ['Linux'] - 'on': ['Linux']
'require': 'require':

View File

@@ -8,13 +8,13 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script. # KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "25") set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "07") set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MICRO "70") set(RELEASE_SERVICE_VERSION_MICRO "80")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}") set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION}) project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
set(KF_MIN_VERSION "6.12") set(KF_MIN_VERSION "6.6")
set(QT_MIN_VERSION "6.5") set(QT_MIN_VERSION "6.5")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE) find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
@@ -56,7 +56,7 @@ ecm_setup_version(${PROJECT_VERSION}
VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h VERSION_HEADER ${CMAKE_CURRENT_BINARY_DIR}/neochat-version.h
) )
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg TextToSpeech WebView) find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Core Quick Gui QuickControls2 Multimedia Svg WebView)
set_package_properties(Qt6 PROPERTIES set_package_properties(Qt6 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Basic application components" PURPOSE "Basic application components"
@@ -75,7 +75,7 @@ set_package_properties(KF6Kirigami PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Kirigami application UI framework" PURPOSE "Kirigami application UI framework"
) )
find_package(KF6KirigamiAddons 1.6.0 REQUIRED) find_package(KF6KirigamiAddons 0.7.2 REQUIRED)
if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE) if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Purpose) find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Purpose)
@@ -107,7 +107,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED) find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif() endif()
find_package(QuotientQt6 0.9.1) find_package(QuotientQt6 0.9)
set_package_properties(QuotientQt6 PROPERTIES set_package_properties(QuotientQt6 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
DESCRIPTION "Qt wrapper around Matrix API" DESCRIPTION "Qt wrapper around Matrix API"
@@ -147,23 +147,16 @@ set_package_properties(KF6DocTools PROPERTIES DESCRIPTION
TYPE OPTIONAL TYPE OPTIONAL
) )
option(WITH_UNIFIEDPUSH "Build with KUnifiedPush support" ON) find_package(KUnifiedPush QUIET)
set_package_properties(KUnifiedPush PROPERTIES
if (ANDROID OR APPLE OR WIN32 OR HAIKU) TYPE OPTIONAL
set(WITH_UNIFIEDPUSH OFF) PURPOSE "Push notification support"
endif() URL "https://invent.kde.org/libraries/kunifiedpush"
)
if (WITH_UNIFIEDPUSH)
find_package(KUnifiedPush)
set_package_properties(KUnifiedPush PROPERTIES
TYPE REQUIRED
PURPOSE "Push notification support"
URL "https://invent.kde.org/libraries/kunifiedpush"
)
endif()
if(ANDROID) if(ANDROID)
find_package(Sqlite3) find_package(Sqlite3)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
endif() endif()
ki18n_install(po) ki18n_install(po)
@@ -180,7 +173,7 @@ add_subdirectory(src)
if (BUILD_TESTING) if (BUILD_TESTING)
find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test) find_package(Qt6 ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
add_subdirectory(autotests) add_subdirectory(autotests)
# add_subdirectory(appiumtests) add_subdirectory(appiumtests)
if (NOT ANDROID) if (NOT ANDROID)
add_subdirectory(memorytests) add_subdirectory(memorytests)
endif() endif()
@@ -191,7 +184,7 @@ if(KF6DocTools_FOUND)
add_subdirectory(doc) add_subdirectory(doc)
endif() endif()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES)
if (NOT ANDROID) if (NOT ANDROID)
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h) file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES src/*.cpp src/*.h)

View File

@@ -42,13 +42,13 @@ SPDX-FileCopyrightText = "2021 Carl Schwan <carlschwan@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause" SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]] [[annotations]]
path = "src/app/neochatconfig.kcfg" path = "src/neochatconfig.kcfg"
precedence = "aggregate" precedence = "aggregate"
SPDX-FileCopyrightText = "2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>" SPDX-FileCopyrightText = "2020-2021 Carl Schwan <carlschwan@kde.org>, Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause" SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]] [[annotations]]
path = "src/app/neochat.notifyrc" path = "src/neochat.notifyrc"
precedence = "aggregate" precedence = "aggregate"
SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>" SPDX-FileCopyrightText = "2020 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause" SPDX-License-Identifier = "BSD-2-Clause"

View File

@@ -23,7 +23,8 @@ repositories {
apply plugin: 'com.android.application' apply plugin: 'com.android.application'
apply from: '../ecm-version.gradle' apply from: '../version.gradle'
def timestamp = (int)(new Date().getTime()/1000)
dependencies { dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar']) implementation fileTree(dir: 'libs', include: ['*.jar', '*.aar'])
@@ -78,9 +79,9 @@ android {
targetSdkVersion qtTargetSdkVersion targetSdkVersion qtTargetSdkVersion
applicationId "org.kde.neochat" applicationId "org.kde.neochat"
namespace "org.kde.neochat" namespace "org.kde.neochat"
versionCode ecmVersionCode versionCode timestamp
versionName ecmVersionName versionName projectVersionFull
manifestPlaceholders = [versionName: ecmVersionName, versionCode: ecmVersionCode] manifestPlaceholders = [versionName: projectVersionFull, versionCode: timestamp]
} }
packagingOptions { packagingOptions {

View File

@@ -0,0 +1,6 @@
// SPDX-FileCopyrightText: 2021 Volker Krause <vkrause@kde.org>
// SPDX-License-Identifier: BSD-3-Clause
ext {
projectVersionFull = "@NEOCHAT_VERSION@"
}

View File

@@ -11,11 +11,13 @@ ecm_add_test(
TEST_NAME neochatroomtest TEST_NAME neochatroomtest
) )
ecm_add_test( if (NOT $ENV{KDE_CI})
texthandlertest.cpp ecm_add_test(
LINK_LIBRARIES neochat Qt::Test texthandlertest.cpp
TEST_NAME texthandlertest LINK_LIBRARIES neochat Qt::Test
) TEST_NAME texthandlertest
)
endif()
ecm_add_test( ecm_add_test(
delegatesizehelpertest.cpp delegatesizehelpertest.cpp

View File

@@ -8,14 +8,14 @@
"answers": [ "answers": [
{ {
"id": "option1", "id": "option1",
"org.matrix.msc1767.text": "option1text" "org.matrix.msc1767.text": "option1"
}, },
{ {
"id": "option2", "id": "option2",
"org.matrix.msc1767.text": "option2text" "org.matrix.msc1767.text": "option2"
} }
], ],
"kind": "org.matrix.msc3381.poll.undisclosed", "kind": "org.matrix.msc3381.poll.disclosed",
"max_selections": 1, "max_selections": 1,
"question": { "question": {
"body": "test", "body": "test",

View File

@@ -2,7 +2,6 @@
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL // SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QObject> #include <QObject>
#include <QQuickItem>
#include <QTest> #include <QTest>
#include "delegatesizehelper.h" #include "delegatesizehelper.h"
@@ -31,7 +30,7 @@ void DelegateSizeHelperTest::risingPercentage_data()
QTest::addColumn<int>("currentPercentageWidth"); QTest::addColumn<int>("currentPercentageWidth");
QTest::addColumn<qreal>("currentWidth"); QTest::addColumn<qreal>("currentWidth");
QTest::newRow("zero") << qreal(0) << int(100) << qreal(0); QTest::newRow("zero") << qreal(0) << int(0) << qreal(0);
QTest::newRow("one hundred") << qreal(100) << int(0) << qreal(0); QTest::newRow("one hundred") << qreal(100) << int(0) << qreal(0);
QTest::newRow("one fifty") << qreal(150) << int(50) << qreal(75); QTest::newRow("one fifty") << qreal(150) << int(50) << qreal(75);
QTest::newRow("two hundred") << qreal(200) << int(100) << qreal(200); QTest::newRow("two hundred") << qreal(200) << int(100) << qreal(200);
@@ -44,18 +43,16 @@ void DelegateSizeHelperTest::risingPercentage()
QFETCH(int, currentPercentageWidth); QFETCH(int, currentPercentageWidth);
QFETCH(qreal, currentWidth); QFETCH(qreal, currentWidth);
auto item = QQuickItem();
item.setWidth(parentWidth);
DelegateSizeHelper delegateSizeHelper; DelegateSizeHelper delegateSizeHelper;
delegateSizeHelper.setParentItem(&item);
delegateSizeHelper.setStartBreakpoint(100); delegateSizeHelper.setStartBreakpoint(100);
delegateSizeHelper.setEndBreakpoint(200); delegateSizeHelper.setEndBreakpoint(200);
delegateSizeHelper.setStartPercentWidth(0); delegateSizeHelper.setStartPercentWidth(0);
delegateSizeHelper.setEndPercentWidth(100); delegateSizeHelper.setEndPercentWidth(100);
QCOMPARE(delegateSizeHelper.availablePercentageWidth(), currentPercentageWidth); delegateSizeHelper.setParentWidth(parentWidth);
QCOMPARE(delegateSizeHelper.availableWidth(), currentWidth);
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
} }
void DelegateSizeHelperTest::fallingPercentage_data() void DelegateSizeHelperTest::fallingPercentage_data()
@@ -77,18 +74,16 @@ void DelegateSizeHelperTest::fallingPercentage()
QFETCH(int, currentPercentageWidth); QFETCH(int, currentPercentageWidth);
QFETCH(qreal, currentWidth); QFETCH(qreal, currentWidth);
auto item = QQuickItem();
item.setWidth(parentWidth);
DelegateSizeHelper delegateSizeHelper; DelegateSizeHelper delegateSizeHelper;
delegateSizeHelper.setParentItem(&item);
delegateSizeHelper.setStartBreakpoint(100); delegateSizeHelper.setStartBreakpoint(100);
delegateSizeHelper.setEndBreakpoint(200); delegateSizeHelper.setEndBreakpoint(200);
delegateSizeHelper.setStartPercentWidth(100); delegateSizeHelper.setStartPercentWidth(100);
delegateSizeHelper.setEndPercentWidth(0); delegateSizeHelper.setEndPercentWidth(0);
QCOMPARE(delegateSizeHelper.availablePercentageWidth(), currentPercentageWidth); delegateSizeHelper.setParentWidth(parentWidth);
QCOMPARE(delegateSizeHelper.availableWidth(), currentWidth);
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
} }
void DelegateSizeHelperTest::equalPercentage_data() void DelegateSizeHelperTest::equalPercentage_data()
@@ -110,18 +105,16 @@ void DelegateSizeHelperTest::equalPercentage()
QFETCH(int, currentPercentageWidth); QFETCH(int, currentPercentageWidth);
QFETCH(qreal, currentWidth); QFETCH(qreal, currentWidth);
auto item = QQuickItem();
item.setWidth(parentWidth);
DelegateSizeHelper delegateSizeHelper; DelegateSizeHelper delegateSizeHelper;
delegateSizeHelper.setParentItem(&item);
delegateSizeHelper.setStartBreakpoint(100); delegateSizeHelper.setStartBreakpoint(100);
delegateSizeHelper.setEndBreakpoint(200); delegateSizeHelper.setEndBreakpoint(200);
delegateSizeHelper.setStartPercentWidth(50); delegateSizeHelper.setStartPercentWidth(50);
delegateSizeHelper.setEndPercentWidth(50); delegateSizeHelper.setEndPercentWidth(50);
QCOMPARE(delegateSizeHelper.availablePercentageWidth(), currentPercentageWidth); delegateSizeHelper.setParentWidth(parentWidth);
QCOMPARE(delegateSizeHelper.availableWidth(), currentWidth);
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
} }
void DelegateSizeHelperTest::equalBreakpoint_data() void DelegateSizeHelperTest::equalBreakpoint_data()
@@ -131,9 +124,9 @@ void DelegateSizeHelperTest::equalBreakpoint_data()
QTest::addColumn<int>("currentPercentageWidth"); QTest::addColumn<int>("currentPercentageWidth");
QTest::addColumn<qreal>("currentWidth"); QTest::addColumn<qreal>("currentWidth");
QTest::newRow("start higher") << int(100) << int(0) << int(100) << qreal(1000); QTest::newRow("start higher") << int(100) << int(0) << int(-1) << qreal(0);
QTest::newRow("equal") << int(50) << int(50) << int(50) << qreal(500); QTest::newRow("equal") << int(50) << int(50) << int(50) << qreal(500);
QTest::newRow("end higher") << int(0) << int(100) << int(100) << qreal(1000); QTest::newRow("end higher") << int(0) << int(100) << int(-1) << qreal(0);
} }
/** /**
@@ -147,18 +140,16 @@ void DelegateSizeHelperTest::equalBreakpoint()
QFETCH(int, currentPercentageWidth); QFETCH(int, currentPercentageWidth);
QFETCH(qreal, currentWidth); QFETCH(qreal, currentWidth);
auto item = QQuickItem();
item.setWidth(1000);
DelegateSizeHelper delegateSizeHelper; DelegateSizeHelper delegateSizeHelper;
delegateSizeHelper.setParentItem(&item);
delegateSizeHelper.setStartBreakpoint(100); delegateSizeHelper.setStartBreakpoint(100);
delegateSizeHelper.setEndBreakpoint(100); delegateSizeHelper.setEndBreakpoint(100);
delegateSizeHelper.setStartPercentWidth(startPercentageWidth); delegateSizeHelper.setStartPercentWidth(startPercentageWidth);
delegateSizeHelper.setEndPercentWidth(endPercentageWidth); delegateSizeHelper.setEndPercentWidth(endPercentageWidth);
QCOMPARE(delegateSizeHelper.availablePercentageWidth(), currentPercentageWidth); delegateSizeHelper.setParentWidth(1000);
QCOMPARE(delegateSizeHelper.availableWidth(), currentWidth);
QCOMPARE(delegateSizeHelper.currentPercentageWidth(), currentPercentageWidth);
QCOMPARE(delegateSizeHelper.currentWidth(), currentWidth);
} }
QTEST_GUILESS_MAIN(DelegateSizeHelperTest) QTEST_GUILESS_MAIN(DelegateSizeHelperTest)

View File

@@ -25,7 +25,6 @@ private Q_SLOTS:
void MediaSizeHelperTest::uninitialized() void MediaSizeHelperTest::uninitialized()
{ {
MediaSizeHelper mediasizehelper; MediaSizeHelper mediasizehelper;
mediasizehelper.setMaxSize(540, 540);
QCOMPARE(mediasizehelper.currentSize(), QSize(540, qRound(qreal(NeoChatConfig::self()->mediaMaxWidth()) / qreal(16.0) * qreal(9.0)))); QCOMPARE(mediasizehelper.currentSize(), QSize(540, qRound(qreal(NeoChatConfig::self()->mediaMaxWidth()) / qreal(16.0) * qreal(9.0))));
} }
@@ -61,7 +60,6 @@ void MediaSizeHelperTest::limits()
QFETCH(QSize, currentSize); QFETCH(QSize, currentSize);
MediaSizeHelper mediasizehelper; MediaSizeHelper mediasizehelper;
mediasizehelper.setMaxSize(540, 540);
mediasizehelper.setMediaWidth(mediaWidth); mediasizehelper.setMediaWidth(mediaWidth);
mediasizehelper.setMediaHeight(mediaHeight); mediasizehelper.setMediaHeight(mediaHeight);
mediasizehelper.setContentMaxWidth(contentMaxWidth); mediasizehelper.setContentMaxWidth(contentMaxWidth);

View File

@@ -12,7 +12,6 @@
#include "models/messagecontentmodel.h" #include "models/messagecontentmodel.h"
#include "neochatconnection.h"
#include "testutils.h" #include "testutils.h"
using namespace Quotient; using namespace Quotient;
@@ -33,7 +32,7 @@ private Q_SLOTS:
void MessageContentModelTest::initTestCase() void MessageContentModelTest::initTestCase()
{ {
connection = new NeoChatConnection; connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
} }
void MessageContentModelTest::missingEvent() void MessageContentModelTest::missingEvent()

View File

@@ -41,32 +41,29 @@ void PollHandlerTest::nullObject()
auto pollHandler = PollHandler(); auto pollHandler = PollHandler();
QCOMPARE(pollHandler.hasEnded(), false); QCOMPARE(pollHandler.hasEnded(), false);
QCOMPARE(pollHandler.numAnswers(), 0); QCOMPARE(pollHandler.answerCount(), 0);
QCOMPARE(pollHandler.question(), QString()); QCOMPARE(pollHandler.question(), QString());
QCOMPARE(pollHandler.kind(), PollKind::Disclosed); QCOMPARE(pollHandler.options(), QJsonArray());
QCOMPARE(pollHandler.answers(), QJsonObject());
QCOMPARE(pollHandler.counts(), QJsonObject());
QCOMPARE(pollHandler.kind(), QString());
} }
void PollHandlerTest::poll() void PollHandlerTest::poll()
{ {
auto startEvent = eventCast<const PollStartEvent>(room->messageEvents().at(0).get()); auto startEvent = eventCast<const PollStartEvent>(room->messageEvents().at(0).get());
auto pollHandler = PollHandler(room, startEvent->id()); auto pollHandler = PollHandler(room, startEvent);
QList<Quotient::EventContent::Answer> options = {EventContent::Answer{"option1"_L1, "option1"_L1}, EventContent::Answer{"option2"_L1, "option2"_L1}}; auto options = QJsonArray{QJsonObject{{"id"_L1, "option1"_L1}, {"org.matrix.msc1767.text"_L1, "option1"_L1}},
QJsonObject{{"id"_L1, "option2"_L1}, {"org.matrix.msc1767.text"_L1, "option2"_L1}}};
const auto answer0 = pollHandler.answerAtRow(0);
const auto answer1 = pollHandler.answerAtRow(1);
QCOMPARE(pollHandler.hasEnded(), false); QCOMPARE(pollHandler.hasEnded(), false);
QCOMPARE(pollHandler.numAnswers(), 2); QCOMPARE(pollHandler.answerCount(), 0);
QCOMPARE(pollHandler.question(), u"test"_s); QCOMPARE(pollHandler.question(), u"test"_s);
QCOMPARE(answer0.id, "option1"_L1); QCOMPARE(pollHandler.options(), options);
QCOMPARE(answer1.id, "option2"_L1); QCOMPARE(pollHandler.answers(), QJsonObject());
QCOMPARE(answer0.text, "option1text"_L1); QCOMPARE(pollHandler.counts(), QJsonObject());
QCOMPARE(answer1.text, "option2text"_L1); QCOMPARE(pollHandler.kind(), u"org.matrix.msc3381.poll.disclosed"_s);
QCOMPARE(pollHandler.answerCountAtId(answer0.id), 0);
QCOMPARE(pollHandler.answerCountAtId(answer1.id), 0);
QCOMPARE(pollHandler.checkMemberSelectedId(connection->userId(), answer0.id), false);
QCOMPARE(pollHandler.checkMemberSelectedId(connection->userId(), answer1.id), false);
QCOMPARE(pollHandler.kind(), PollKind::Undisclosed);
} }
QTEST_GUILESS_MAIN(PollHandlerTest) QTEST_GUILESS_MAIN(PollHandlerTest)

View File

@@ -9,7 +9,6 @@
#include <Quotient/events/roommessageevent.h> #include <Quotient/events/roommessageevent.h>
#include "models/messagecontentmodel.h"
#include "testutils.h" #include "testutils.h"
using namespace Quotient; using namespace Quotient;

View File

@@ -8,8 +8,7 @@
#include <Quotient/quotient_common.h> #include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h> #include <Quotient/syncdata.h>
#include <qnamespace.h>
#include <Kirigami/Platform/PlatformTheme>
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"
#include "models/customemojimodel.h" #include "models/customemojimodel.h"
@@ -44,8 +43,6 @@ private Q_SLOTS:
void sendCustomEmoji(); void sendCustomEmoji();
void sendCustomEmojiCode_data(); void sendCustomEmojiCode_data();
void sendCustomEmojiCode(); void sendCustomEmojiCode();
void sendCustomTags_data();
void sendCustomTags();
void receiveSpacelessSelfClosingTag(); void receiveSpacelessSelfClosingTag();
void receiveStripReply(); void receiveStripReply();
@@ -252,38 +249,6 @@ void TextHandlerTest::sendCustomEmojiCode()
QCOMPARE(testTextHandler.handleSendText(), testOutputString); QCOMPARE(testTextHandler.handleSendText(), testOutputString);
} }
void TextHandlerTest::sendCustomTags_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString");
// spoiler
QTest::newRow("incomplete spoiler") << u"||test"_s << u"||test"_s;
QTest::newRow("complete spoiler") << u"||test||"_s << u"<span data-mx-spoiler>test</span>"_s;
QTest::newRow("multiple spoiler") << u"||apple||banana||pear||"_s << u"<span data-mx-spoiler>apple</span>banana<span data-mx-spoiler>pear</span>"_s;
QTest::newRow("inside code block spoiler") << u"```||apple||```"_s << u"<code>||apple||</code>"_s;
QTest::newRow("outside code block spoiler") << u"||apple|| ```||banana||``` ||pear||"_s
<< u"<span data-mx-spoiler>apple</span> <code>||banana||</code> <span data-mx-spoiler>pear</span>"_s;
// strikethrough
QTest::newRow("incomplete strikethrough") << u"~~test"_s << u"~~test"_s;
QTest::newRow("complete strikethrough") << u"~~test~~"_s << u"<del>test</del>"_s;
QTest::newRow("inside code block strikethrough") << u"```~~apple~~```"_s << u"<code>~~apple~~</code>"_s;
QTest::newRow("outside code block strikethrough") << u"~~apple~~ ```~~banana~~``` ~~pear~~"_s
<< u"<del>apple</del> <code>~~banana~~</code> <del>pear</del>"_s;
}
void TextHandlerTest::sendCustomTags()
{
QFETCH(QString, testInputString);
QFETCH(QString, testOutputString);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
}
void TextHandlerTest::receiveSpacelessSelfClosingTag() void TextHandlerTest::receiveSpacelessSelfClosingTag()
{ {
const QString testInputString = u"Test...<br/>...ing"_s; const QString testInputString = u"Test...<br/>...ing"_s;
@@ -503,12 +468,9 @@ void TextHandlerTest::receiveRichEdited_data()
QTest::addColumn<QString>("testInputString"); QTest::addColumn<QString>("testInputString");
QTest::addColumn<QString>("testOutputString"); QTest::addColumn<QString>("testOutputString");
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true)); QTest::newRow("basic") << u"Edited"_s << u"Edited <span style=\"color:#000000\">(edited)</span>"_s;
QTest::newRow("basic") << u"Edited"_s << u"Edited <span style=\"color:%1\">(edited)</span>"_s.arg(theme ? theme->disabledTextColor().name() : u"#000000"_s);
QTest::newRow("multiple paragraphs") << u"<p>Edited</p>\n<p>Edited</p>"_s QTest::newRow("multiple paragraphs") << u"<p>Edited</p>\n<p>Edited</p>"_s
<< u"<p>Edited</p>\n<p>Edited <span style=\"color:%1\">(edited)</span></p>"_s.arg( << u"<p>Edited</p>\n<p>Edited <span style=\"color:#000000\">(edited)</span></p>"_s;
theme ? theme->disabledTextColor().name() : u"#000000"_s);
} }
void TextHandlerTest::receiveRichEdited() void TextHandlerTest::receiveRichEdited()
@@ -549,6 +511,8 @@ void TextHandlerTest::receiveRichColor()
TextHandler testTextHandler; TextHandler testTextHandler;
testTextHandler.setData(testInputString); testTextHandler.setData(testInputString);
qInfo() << testTextHandler.handleRecieveRichText();
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString); QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
} }
@@ -566,9 +530,6 @@ void TextHandlerTest::componentOutput_data()
QTest::newRow("quote") << u"<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>"_s QTest::newRow("quote") << u"<p>Text</p>\n<blockquote>\n<p>blockquote</p>\n</blockquote>"_s
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, << QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}},
MessageComponent{MessageComponentType::Quote, u"“blockquote”"_s, {}}}; MessageComponent{MessageComponentType::Quote, u"“blockquote”"_s, {}}};
QTest::newRow("multiple paragraph quote") << u"<blockquote>\n<p>blockquote</p>\n<p>next paragraph</p>\n</blockquote>"_s
<< QList<MessageComponent>{
MessageComponent{MessageComponentType::Quote, u"<p>“blockquote</p>\n<p>next paragraph”</p>"_s, {}}};
QTest::newRow("no tag first paragraph") << u"Text\n<p>Text</p>"_s QTest::newRow("no tag first paragraph") << u"Text\n<p>Text</p>"_s
<< QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}}, << QList<MessageComponent>{MessageComponent{MessageComponentType::Text, u"Text"_s, {}},
MessageComponent{MessageComponentType::Text, u"Text"_s, {}}}; MessageComponent{MessageComponentType::Text, u"Text"_s, {}}};

View File

@@ -7,19 +7,16 @@ qt_add_executable(timeline-memtest
main.cpp main.cpp
) )
target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin) target_link_libraries(timeline-memtest PRIVATE neochatplugin timelineplugin)
target_link_libraries(timeline-memtest PUBLIC target_link_libraries(timeline-memtest PUBLIC
Qt::Core Qt::Core
Qt::Quick Qt::Quick
Qt::Qml Qt::Qml
Qt::Gui Qt::Gui
Qt::QuickControls2 Qt::QuickControls2
Qt::Widgets
KF6::I18nQml
KF6::Kirigami KF6::Kirigami
QuotientQt6 QuotientQt6
LibNeoChat neochat
Timeline
) )
ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE
@@ -33,5 +30,5 @@ ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERAT
QtCore QtCore
QtQuick QtQuick
IMPORTS IMPORTS
org.kde.neochat.timeline org.kde.neochat
) )

View File

@@ -23,7 +23,6 @@ QQC2.ApplicationWindow {
height: root.height height: root.height
contentItem: ListView { contentItem: ListView {
cacheBuffer: 1000000
model: messageFilterModel model: messageFilterModel
delegate: EventDelegate { delegate: EventDelegate {

View File

@@ -5,7 +5,6 @@
#include <QQmlApplicationEngine> #include <QQmlApplicationEngine>
#include <QQmlContext> #include <QQmlContext>
#include <KLocalizedQmlContext>
#include <KLocalizedString> #include <KLocalizedString>
#include "memtesttimelinemodel.h" #include "memtesttimelinemodel.h"
@@ -20,15 +19,12 @@ int main(int argc, char **argv)
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat")); KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
QQmlApplicationEngine engine; QQmlApplicationEngine engine;
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
KLocalization::setupLocalizedContext(&engine);
MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel; MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel;
MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel); MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel); engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel); engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
return app.exec(); return app.exec();
} }

View File

@@ -241,7 +241,7 @@
"formatted_body": "This is an example<br>text message", "formatted_body": "This is an example<br>text message",
"msgtype": "m.text" "msgtype": "m.text"
}, },
"event_id": 0, "event_id": "$1000000000000:example.org",
"origin_server_ts": 1000000000000, "origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org", "sender": "@example:example.org",
@@ -255,7 +255,7 @@
"body": "This is a highlight @bob:example.org", "body": "This is a highlight @bob:example.org",
"msgtype": "m.text" "msgtype": "m.text"
}, },
"event_id": 1, "event_id": "$1000000000001:example.org",
"origin_server_ts": 1000000000001, "origin_server_ts": 1000000000001,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org", "sender": "@example:example.org",
@@ -266,11 +266,11 @@
}, },
{ {
"content": { "content": {
"m.relates_to": { "m.relates_to": {
"event_id": 1, "event_id": "$1000000000001:example.org",
"key": "👍", "key": "👍",
"rel_type": "m.annotation" "rel_type": "m.annotation"
} }
}, },
"origin_server_ts": 1000000000002, "origin_server_ts": 1000000000002,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
@@ -279,7 +279,7 @@
"unsigned": { "unsigned": {
"age": 390159120 "age": 390159120
}, },
"event_id": 2, "event_id": "$1000000000002:example.org",
"age": 390159120 "age": 390159120
}, },
{ {
@@ -289,7 +289,7 @@
"formatted_body": "reply", "formatted_body": "reply",
"m.relates_to": { "m.relates_to": {
"m.in_reply_to": { "m.in_reply_to": {
"event_id": 0 "event_id": "$1000000000000:example.org"
} }
}, },
"msgtype": "m.text" "msgtype": "m.text"
@@ -300,7 +300,7 @@
"unsigned": { "unsigned": {
"age": 98 "age": 98
}, },
"event_id": 3, "event_id": "$1000000000003:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org" "room_id": "!jEsUZKDJdhlrceRyVU:example.org"
}, },
{ {
@@ -317,7 +317,7 @@
"uri": "geo:51.7035,-1.14394" "uri": "geo:51.7035,-1.14394"
} }
}, },
"event_id": 4, "event_id": "$1000000000004:example.org",
"origin_server_ts": 1000000000004, "origin_server_ts": 1000000000004,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org", "sender": "@example:example.org",
@@ -333,7 +333,7 @@
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(&quot;neochat&quot;));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(&quot;org.kde.neochat.timeline-memtest&quot;, &quot;Main&quot;);\n\n return app.exec();\n}\n</code></pre>", "formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(&quot;neochat&quot;));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(&quot;org.kde.neochat.timeline-memtest&quot;, &quot;Main&quot;);\n\n return app.exec();\n}\n</code></pre>",
"msgtype": "m.text" "msgtype": "m.text"
}, },
"event_id": 5, "event_id": "$1000000000005:example.org",
"origin_server_ts": 1000000000005, "origin_server_ts": 1000000000005,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org", "sender": "@bob:example.org",
@@ -347,7 +347,7 @@
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ", "body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"msgtype": "m.text" "msgtype": "m.text"
}, },
"event_id": 6, "event_id": "$1000000000006:example.org",
"origin_server_ts": 1000000000006, "origin_server_ts": 1000000000006,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org", "sender": "@example:example.org",
@@ -363,7 +363,7 @@
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>", "formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
"msgtype": "m.text" "msgtype": "m.text"
}, },
"event_id": 7, "event_id": "$1000000000007:example.org",
"origin_server_ts": 1000000000007, "origin_server_ts": 1000000000007,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org", "room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org", "sender": "@example:example.org",
@@ -371,6 +371,282 @@
"unsigned": { "unsigned": {
"age": 1232 "age": 1232
} }
},
{
"content": {
"body": "This is an example text message",
"format": "org.matrix.custom.html",
"formatted_body": "This is an example<br>text message",
"msgtype": "m.text"
},
"event_id": "$1000000000008:example.org",
"origin_server_ts": 1000000000008,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is a highlight @bob:example.org",
"msgtype": "m.text"
},
"event_id": "$1000000000009:example.org",
"origin_server_ts": 1000000000009,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$1000000000009:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1000000000010,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$1000000000010:example.org",
"age": 390159120
},
{
"content": {
"body": "reply",
"format": "org.matrix.custom.html",
"formatted_body": "reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$1000000000008:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1000000000011,
"sender": "@alice:example.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$1000000000011:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1000000000012:example.org",
"origin_server_ts": 1000000000012,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
},
{
"content": {
"body": "```cpp\nint main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(\"neochat\"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(\"org.kde.neochat.timeline-memtest\", \"Main\");\n\n return app.exec();\n}\n```",
"format": "org.matrix.custom.html",
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(&quot;neochat&quot;));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(&quot;org.kde.neochat.timeline-memtest&quot;, &quot;Main&quot;);\n\n return app.exec();\n}\n</code></pre>",
"msgtype": "m.text"
},
"event_id": "$1000000000013:example.org",
"origin_server_ts": 1000000000013,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"msgtype": "m.text"
},
"event_id": "$1000000000014:example.org",
"origin_server_ts": 1000000000014,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"format": "org.matrix.custom.html",
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
"msgtype": "m.text"
},
"event_id": "$1000000000015:example.org",
"origin_server_ts": 1000000000015,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is an example text message",
"format": "org.matrix.custom.html",
"formatted_body": "This is an example<br>text message",
"msgtype": "m.text"
},
"event_id": "$1000000000016:example.org",
"origin_server_ts": 1000000000016,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "This is a highlight @bob:example.org",
"msgtype": "m.text"
},
"event_id": "$1000000000017:example.org",
"origin_server_ts": 1000000000017,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$1000000000017:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1000000000018,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$1000000000018:example.org",
"age": 390159120
},
{
"content": {
"body": "reply",
"format": "org.matrix.custom.html",
"formatted_body": "reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$1000000000016:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1000000000019,
"sender": "@alice:example.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$1000000000019:example.org",
"room_id": "!jEsUZKDJdhlrceRyVU:example.org"
},
{
"age": 96845207,
"content": {
"body": "Lat: 51.7035, Lon: -1.14394",
"geo_uri": "geo:51.7035,-1.14394",
"msgtype": "m.location",
"org.matrix.msc1767.text": "Lat: 51.7035, Lon: -1.14394",
"org.matrix.msc3488.asset": {
"type": "m.pin"
},
"org.matrix.msc3488.location": {
"uri": "geo:51.7035,-1.14394"
}
},
"event_id": "$1000000000020:example.org",
"origin_server_ts": 1000000000020,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 96845207
}
},
{
"content": {
"body": "```cpp\nint main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(\"neochat\"));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(\"org.kde.neochat.timeline-memtest\", \"Main\");\n\n return app.exec();\n}\n```",
"format": "org.matrix.custom.html",
"formatted_body": "<pre><code class=\"language-cpp\">int main(int argc, char **argv)\n{\n QApplication app(argc, argv);\n\n KLocalizedString::setApplicationDomain(QByteArrayLiteral(&quot;neochat&quot;));\n\n QQmlApplicationEngine engine;\n engine.loadFromModule(&quot;org.kde.neochat.timeline-memtest&quot;, &quot;Main&quot;);\n\n return app.exec();\n}\n</code></pre>",
"msgtype": "m.text"
},
"event_id": "$1000000000021:example.org",
"origin_server_ts": 1000000000021,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@bob:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"body": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"msgtype": "m.text"
},
"event_id": "$1000000000022:example.org",
"origin_server_ts": 1000000000022,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
},
{
"content": {
"body": "> Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat. ",
"format": "org.matrix.custom.html",
"formatted_body": "<blockquote>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sed fringilla risus, eget lacinia risus. Suspendisse at magna id justo sagittis suscipit. Maecenas eros quam, pulvinar a consequat sed, varius vitae risus. Cras congue est eget felis porttitor lobortis. Nam cursus, nulla ut finibus suscipit, tellus eros tincidunt ante, a volutpat velit lectus sit amet turpis. Morbi leo justo, fringilla sed rutrum a, suscipit a quam. Proin rhoncus neque eget ligula ullamcorper pellentesque. Mauris volutpat malesuada nunc. Nullam finibus enim eu nibh placerat imperdiet. Nullam in mi in diam luctus scelerisque dignissim non erat.</p>\n</blockquote>",
"msgtype": "m.text"
},
"event_id": "$1000000000023:example.org",
"origin_server_ts": 1000000000023,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1232
}
} }
], ],
"limited": true, "limited": true,

View File

@@ -39,52 +39,10 @@ public:
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName); testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
testSyncFile.open(QIODevice::ReadOnly); testSyncFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object(); auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
auto timelineJson = testSyncJson["timeline"_L1].toObject();
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
testSyncJson["timeline"_L1] = timelineJson;
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson); Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson);
update(std::move(roomData)); update(std::move(roomData));
} }
} }
QJsonArray multiplyEvents(QJsonArray events, int factor)
{
QJsonArray newArray;
int eventNum = 0;
int ts = 0;
for (int i = 0; i < factor; ++i) {
for (const auto &event : events) {
auto eventObject = event.toObject();
auto contentJson = eventObject["content"_L1].toObject();
if (contentJson.contains("m.relates_to"_L1)) {
auto relatesToJson = contentJson["m.relates_to"_L1].toObject();
if (relatesToJson.contains("m.in_reply_to"_L1)) {
auto replyJson = relatesToJson["m.in_reply_to"_L1].toObject();
const auto currentId = eventObject["event_id"_L1].toInt();
const auto currentReplyId = replyJson["event_id"_L1].toInt();
replyJson["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum - (currentId - currentReplyId)));
relatesToJson["m.in_reply_to"_L1] = replyJson;
} else if (relatesToJson.contains("event_id"_L1)) {
const auto currentId = eventObject["event_id"_L1].toInt();
const auto currentRelationId = relatesToJson["event_id"_L1].toInt();
relatesToJson["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum - (currentId - currentRelationId)));
}
contentJson["m.relates_to"_L1] = relatesToJson;
eventObject["content"_L1] = contentJson;
}
eventObject["event_id"_L1] = "$%1:example.org"_L1.arg(QString::number(eventNum));
eventObject["origin_server_ts"_L1] = ts;
auto unsignedJson = eventObject["unsigned"_L1].toObject();
unsignedJson["age"_L1] = ts;
eventObject["unsigned"_L1] = unsignedJson;
newArray.append(eventObject);
++eventNum;
++ts;
}
}
return newArray;
}
}; };
/** /**

View File

@@ -70,12 +70,10 @@
<summary xml:lang="ia">Conversation en ditecto sur Matrix</summary> <summary xml:lang="ia">Conversation en ditecto sur Matrix</summary>
<summary xml:lang="it">Chat su Matrix</summary> <summary xml:lang="it">Chat su Matrix</summary>
<summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary> <summary xml:lang="ka">ისაუბრეთ Matrix-ზე</summary>
<summary xml:lang="ko">Matrix에서 대화하기</summary>
<summary xml:lang="lv">Tērzējiet „Matrix“ tīklā</summary> <summary xml:lang="lv">Tērzējiet „Matrix“ tīklā</summary>
<summary xml:lang="nl">Chat op Matrix</summary> <summary xml:lang="nl">Chat op Matrix</summary>
<summary xml:lang="nn">Prat med via Matrix</summary> <summary xml:lang="nn">Prat med via Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary> <summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
<summary xml:lang="ru">Общение в Matrix</summary>
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary> <summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary> <summary xml:lang="sl">Klepet na Matrixu</summary>
<summary xml:lang="sv">Chatta på Matrix</summary> <summary xml:lang="sv">Chatta på Matrix</summary>
@@ -104,7 +102,6 @@
<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="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> <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>
<p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p> <p xml:lang="ka">NeoChat ჩატის აპია, რომელიც საშუალება გაძლევთ, Matrix-ის ქსელის საშუალებები ბოლომდე გამოიყენოთ. ის გაძლევთ უსაფრთხო გზას, გააგზავნოთ ტექსტური შეტყობინებები, ვიდეოებ და აუდიოფაილები თქვენს ოჯახთან, კოლეგებთან და მეგობრებთან.</p>
<p xml:lang="ko">NeoChat은 Matrix 네트워크를 사용하는 채팅 앱입니다. 텍스트 메시지, 동영상, 오디오 파일을 가족, 친구, 동료와 안전하게 공유할 수 있습니다.</p>
<p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p> <p xml:lang="lv">„NeoChat“ ir tērzēšanas programma, kas ļauj pilnvērtīgi izmantot „Matrix“ tīklu. Tā sniedz drošu veidu teksta ziņu, video un audio sūtīšanai ģimenes locekļiem, kolēģiem un draugiem.</p>
<p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p> <p xml:lang="nl">NeoChat is een chat-toepassing die u het volledige voordeel van het Matrix-netwerk laat genieten. Het levert u op een veilige manier tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden te verzenden.</p>
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p> <p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
@@ -375,7 +372,6 @@
<caption xml:lang="ia">Discoperi nove communitate con Matrix Spaces (Spatios de Matrix)</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> <caption xml:lang="it">Scopri nuove comunità con Matrix Spaces</caption>
<caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption> <caption xml:lang="ka">აღმოაჩინეთ ახალი საზოგადოებები Matrix Spaces-თან ერთად</caption>
<caption xml:lang="ko">Matrix 스페이스에서 새로운 커뮤니티 탐험</caption>
<caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption> <caption xml:lang="lv">Atklājiet jaunas kopienas ar „Matrix“ telpām</caption>
<caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption> <caption xml:lang="nl">Ontdek nieuwe gemeenschappen met Matrix-ruimten</caption>
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption> <caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
@@ -477,8 +473,6 @@
<content_attribute id="social-chat">intense</content_attribute> <content_attribute id="social-chat">intense</content_attribute>
</content_rating> </content_rating>
<releases> <releases>
<release version="25.04.1" date="2025-05-08"/>
<release version="25.04.0" date="2025-04-17"/>
<release version="24.12.3" date="2025-03-06"/> <release version="24.12.3" date="2025-03-06"/>
<release version="24.12.2" date="2025-02-06"/> <release version="24.12.2" date="2025-02-06"/>
<release version="24.12.1" date="2025-01-09"/> <release version="24.12.1" date="2025-01-09"/>

View File

@@ -109,12 +109,10 @@ Comment[hu]=Csevegés Matrixon
Comment[ia]=Conversation en ditecto sur Matrix Comment[ia]=Conversation en ditecto sur Matrix
Comment[it]= su Matrix Comment[it]= su Matrix
Comment[ka]=ჩატი Matrix-ზე Comment[ka]=ჩატი Matrix-ზე
Comment[ko]=Matrix에서 대화하기
Comment[lv]=Tērzējiet „Matrix“ tīklā Comment[lv]=Tērzējiet „Matrix“ tīklā
Comment[nl]=Chat op Matrix Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie Comment[pl]=Rozmawiaj na Matriksie
Comment[pt_BR]=Bate papo na Matrix Comment[pt_BR]=Bate papo na Matrix
Comment[ru]=Общение в Matrix
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
Comment[sl]=Klepet na Matrixu Comment[sl]=Klepet na Matrixu
Comment[sv]=Chatta på Matrix Comment[sv]=Chatta på Matrix

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

@@ -1,4 +1,4 @@
# SPDX-FileCopyrightText: 2024-2025 Scarlett Moore <sgmoore@kde.org> # SPDX-FileCopyrightText: 2024 Scarlett Moore <sgmoore@kde.org>
# #
# SPDX-License-Identifier: CC0-1.0 # SPDX-License-Identifier: CC0-1.0
--- ---
@@ -24,9 +24,6 @@ apps:
- network-manager-observe - network-manager-observe
- password-manager-service - password-manager-service
- accounts-service - accounts-service
environment:
QT_PLUGIN_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/plugins/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/plugins"
QML_IMPORT_PATH: "$SNAP/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml:/snap/kf6-core24/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/qml"
compression: lzo compression: lzo
@@ -44,12 +41,11 @@ parts:
olm: olm:
source: https://gitlab.matrix.org/matrix-org/olm.git source: https://gitlab.matrix.org/matrix-org/olm.git
source-depth: 1 source-depth: 1
source-tag: '3.2.16' source-tag: '3.2.12'
plugin: cmake plugin: cmake
cmake-parameters: cmake-parameters:
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_POLICY_VERSION_MINIMUM=3.5
prime: prime:
- -usr/include - -usr/include
- -usr/lib/*/pkgconfig - -usr/lib/*/pkgconfig
@@ -85,7 +81,8 @@ parts:
plugin: cmake plugin: cmake
build-environment: build-environment:
- PATH: /snap/bin:${PATH} - PATH: /snap/bin:${PATH}
- PKG_CONFIG_PATH: "$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET/pkgconfig:$PKG_CONFIG_PATH" - PKG_CONFIG_PATH: "$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET\
/pkgconfig:$PKG_CONFIG_PATH"
cmake-parameters: cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
@@ -101,7 +98,7 @@ parts:
- olm - olm
- qtkeychain - qtkeychain
source: https://github.com/quotient-im/libQuotient.git source: https://github.com/quotient-im/libQuotient.git
source-tag: 0.9.2 source-tag: 0.9.1
source-depth: 1 source-depth: 1
plugin: cmake plugin: cmake
build-environment: build-environment:
@@ -140,24 +137,11 @@ parts:
- -usr/lib/*/pkgconfig - -usr/lib/*/pkgconfig
- -usr/lib/*/cmake - -usr/lib/*/cmake
kunifiedpush:
source: https://invent.kde.org/libraries/kunifiedpush.git
plugin: cmake
build-environment:
- PATH: /snap/bin:${PATH}
- PYTHONPATH: ${CRAFT_STAGE}/lib/python3.12/site-packages:${CRAFT_STAGE}/usr/lib/python3/dist-packages
- LD_LIBRARY_PATH: "/snap/mesa-2404/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:$CRAFT_STAGE/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR:/snap/kde-qt6-core24-sdk/current/usr/lib/$CRAFT_ARCH_TRIPLET_BUILD_FOR/libproxy:$LD_LIBRARY_PATH"
cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release
- -DBUILD_TESTING=OFF
neochat: neochat:
after: after:
- qtkeychain - qtkeychain
- libquotient - libquotient
- kquickimageeditor - kquickimageeditor
- kunifiedpush
parse-info: parse-info:
- usr/share/metainfo/org.kde.neochat.appdata.xml - usr/share/metainfo/org.kde.neochat.appdata.xml
source: . source: .

View File

@@ -1,16 +1,580 @@
# SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com> # SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carl@carlschwan.eu>
# SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE) if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE)
add_subdirectory(purpose) add_subdirectory(purpose)
endif() endif()
add_subdirectory(libneochat) add_library(neochat STATIC
add_subdirectory(login) controller.cpp
add_subdirectory(rooms) controller.h
add_subdirectory(timeline) models/emojimodel.cpp
add_subdirectory(spaces) models/emojimodel.h
add_subdirectory(chatbar) emojitones.cpp
emojitones.h
models/customemojimodel.cpp
models/customemojimodel.h
clipboard.cpp
clipboard.h
models/timelinemessagemodel.cpp
models/timelinemessagemodel.h
models/messagefiltermodel.cpp
models/messagefiltermodel.h
models/roomlistmodel.cpp
models/roomlistmodel.h
models/sortfilterspacelistmodel.cpp
models/sortfilterspacelistmodel.h
models/accountemoticonmodel.cpp
models/accountemoticonmodel.h
spacehierarchycache.cpp
spacehierarchycache.h
roommanager.cpp
roommanager.h
neochatroom.cpp
neochatroom.h
models/userlistmodel.cpp
models/userlistmodel.h
models/userfiltermodel.cpp
models/userfiltermodel.h
models/publicroomlistmodel.cpp
models/publicroomlistmodel.h
models/spacechildrenmodel.cpp
models/spacechildrenmodel.h
models/spacechildsortfiltermodel.cpp
models/spacechildsortfiltermodel.h
models/spacetreeitem.cpp
models/spacetreeitem.h
models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h
models/pushrulemodel.cpp
models/pushrulemodel.h
models/emoticonfiltermodel.cpp
models/emoticonfiltermodel.h
notificationsmanager.cpp
notificationsmanager.h
models/sortfilterroomlistmodel.cpp
models/sortfilterroomlistmodel.h
models/roomtreemodel.cpp
models/roomtreemodel.h
chatdocumenthandler.cpp
chatdocumenthandler.h
models/devicesmodel.cpp
models/devicesmodel.h
models/devicesproxymodel.cpp
filetype.cpp
filetype.h
login.cpp
login.h
models/webshortcutmodel.cpp
models/webshortcutmodel.h
blurhash.cpp
blurhash.h
blurhashimageprovider.cpp
blurhashimageprovider.h
models/mediamessagefiltermodel.cpp
models/mediamessagefiltermodel.h
urlhelper.cpp
urlhelper.h
windowcontroller.cpp
windowcontroller.h
linkpreviewer.cpp
linkpreviewer.h
models/completionmodel.cpp
models/completionmodel.h
models/completionproxymodel.cpp
models/completionproxymodel.h
models/actionsmodel.cpp
models/actionsmodel.h
models/serverlistmodel.cpp
models/serverlistmodel.h
models/statemodel.cpp
models/statemodel.h
models/statefiltermodel.cpp
models/statefiltermodel.h
filetransferpseudojob.cpp
filetransferpseudojob.h
models/searchmodel.cpp
models/searchmodel.h
texthandler.cpp
texthandler.h
logger.cpp
logger.h
models/stickermodel.cpp
models/stickermodel.h
models/imagepacksmodel.cpp
models/imagepacksmodel.h
events/imagepackevent.cpp
events/imagepackevent.h
models/reactionmodel.cpp
models/reactionmodel.h
delegatesizehelper.cpp
delegatesizehelper.h
models/livelocationsmodel.cpp
models/livelocationsmodel.h
models/locationsmodel.cpp
models/locationsmodel.h
locationhelper.cpp
locationhelper.h
events/pollevent.cpp
pollhandler.cpp
utils.h
utils.cpp
registration.cpp
neochatconnection.cpp
neochatconnection.h
jobs/neochatgetcommonroomsjob.cpp
jobs/neochatgetcommonroomsjob.h
mediasizehelper.cpp
mediasizehelper.h
eventhandler.cpp
enums/delegatetype.h
roomlastmessageprovider.cpp
roomlastmessageprovider.h
chatbarcache.cpp
chatbarcache.h
colorschemer.cpp
colorschemer.h
models/notificationsmodel.cpp
models/notificationsmodel.h
models/timelinemodel.cpp
models/timelinemodel.h
enums/pushrule.h
models/itinerarymodel.cpp
models/itinerarymodel.h
proxycontroller.cpp
proxycontroller.h
models/linemodel.cpp
models/linemodel.h
events/locationbeaconevent.h
events/widgetevent.h
enums/messagecomponenttype.h
models/messagecontentmodel.cpp
models/messagecontentmodel.h
enums/neochatroomtype.h
models/sortfilterroomtreemodel.cpp
models/sortfilterroomtreemodel.h
mediamanager.cpp
mediamanager.h
models/statekeysmodel.cpp
models/statekeysmodel.h
sharehandler.cpp
sharehandler.h
models/roomtreeitem.cpp
models/roomtreeitem.h
foreigntypes.h
models/threepidmodel.cpp
models/threepidmodel.h
threepidaddhelper.cpp
threepidaddhelper.h
identityserverhelper.cpp
identityserverhelper.h
enums/powerlevel.cpp
enums/powerlevel.h
models/permissionsmodel.cpp
models/permissionsmodel.h
threepidbindhelper.cpp
threepidbindhelper.h
models/readmarkermodel.cpp
models/readmarkermodel.h
neochatroommember.cpp
neochatroommember.h
models/threadmodel.cpp
models/threadmodel.h
enums/messagetype.h
messagecomponent.h
enums/roomsortparameter.cpp
enums/roomsortparameter.h
models/roomsortparametermodel.cpp
models/roomsortparametermodel.h
models/messagemodel.cpp
models/messagemodel.h
models/messagecontentfiltermodel.cpp
models/messagecontentfiltermodel.h
models/pinnedmessagemodel.cpp
models/pinnedmessagemodel.h
models/commonroomsmodel.cpp
models/commonroomsmodel.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES
qml/Main.qml
qml/AccountMenu.qml
qml/ExploreComponent.qml
qml/ExploreComponentMobile.qml
qml/RoomContextMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomDelegate.qml
qml/RoomListPage.qml
qml/SpaceListContextMenu.qml
qml/UserInfo.qml
qml/UserInfoDesktop.qml
qml/RoomPage.qml
qml/ExploreRoomsPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/ImageEditorPage.qml
qml/NeochatMaximizeComponent.qml
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/HoverActions.qml
qml/AttachmentPane.qml
qml/QuickFormatBar.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
qml/PowerLevelDialog.qml
qml/Message.qml
qml/EmojiItem.qml
qml/EmojiRow.qml
qml/EmojiSas.qml
qml/ConfirmDeactivateAccountDialog.qml
qml/VerificationCanceled.qml
qml/MessageDelegateContextMenu.qml
qml/FileDelegateContextMenu.qml
qml/MessageSourceSheet.qml
qml/ConfirmEncryptionDialog.qml
qml/RoomSearchPage.qml
qml/RoomPinnedMessagesPage.qml
qml/LocationChooser.qml
qml/TimelineView.qml
qml/InvitationView.qml
qml/AvatarTabButton.qml
qml/SpaceDrawer.qml
qml/OsmLocationPlugin.qml
qml/FullScreenMap.qml
qml/LocationsPage.qml
qml/LocationMapItem.qml
qml/RoomDrawer.qml
qml/RoomDrawerPage.qml
qml/DirectChatDrawerHeader.qml
qml/GroupChatDrawerHeader.qml
qml/RoomInformation.qml
qml/RoomMedia.qml
qml/ChooseRoomDialog.qml
qml/SpaceHomePage.qml
qml/SpaceHierarchyDelegate.qml
qml/RemoveChildDialog.qml
qml/SelectParentDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/SelectSpacesDialog.qml
qml/NotificationsView.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
qml/UserSearchPage.qml
qml/ManualUserDialog.qml
qml/RecommendedSpaceDialog.qml
qml/RoomTreeSection.qml
qml/DelegateContextMenu.qml
qml/ShareDialog.qml
qml/UnlockSSSSDialog.qml
qml/QrScannerPage.qml
qml/JoinRoomDialog.qml
qml/ConfirmUrlDialog.qml
qml/AccountSwitchDialog.qml
qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml
qml/EditStateDialog.qml
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
qml/AvatarNotification.qml
qml/ReasonDialog.qml
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat.timeline
org.kde.neochat.settings
org.kde.neochat.devtools
org.kde.neochat.login
org.kde.neochat.chatbar
)
add_subdirectory(settings) add_subdirectory(settings)
add_subdirectory(timeline)
add_subdirectory(devtools) add_subdirectory(devtools)
add_subdirectory(app) add_subdirectory(login)
add_subdirectory(chatbar)
if(NOT ANDROID AND NOT WIN32)
qt_target_qml_sources(neochat QML_FILES
qml/ShareAction.qml
qml/GlobalMenu.qml
qml/EditMenu.qml
)
else()
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
endif()
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
ecm_qt_declare_logging_category(neochat
HEADER "messagemodel_logging.h"
IDENTIFIER "Message"
CATEGORY_NAME "org.kde.neochat.messagemodel"
DESCRIPTION "Neochat: messagemodel"
DEFAULT_SEVERITY Info
EXPORT NEOCHAT
)
ecm_qt_declare_logging_category(neochat
HEADER "publicroomlist_logging.h"
IDENTIFIER "PublicRoomList"
CATEGORY_NAME "org.kde.neochat.publicroomlistmodel"
DESCRIPTION "Neochat: publicroomlistmodel"
DEFAULT_SEVERITY Info
EXPORT NEOCHAT
)
ecm_qt_declare_logging_category(neochat
HEADER "eventhandler_logging.h"
IDENTIFIER "EventHandling"
CATEGORY_NAME "org.kde.neochat.eventhandler"
DEFAULT_SEVERITY Info
)
ecm_qt_declare_logging_category(neochat
HEADER "chatdocumenthandler_logging.h"
IDENTIFIER "ChatDocumentHandling"
CATEGORY_NAME "org.kde.neochat.chatdocumenthandler"
DEFAULT_SEVERITY Info
)
add_executable(neochat-app
main.cpp
)
if(TARGET Qt::WebView)
target_link_libraries(neochat-app PUBLIC Qt::WebView)
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
endif()
target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat-app PRIVATE
neochat
)
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
if(NOT ANDROID)
if (NOT WIN32 AND NOT APPLE)
target_sources(neochat PRIVATE trayicon_sni.cpp trayicon_sni.h)
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
else()
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
endif()
target_link_libraries(neochat PUBLIC KF6::WindowSystem ICU::uc)
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
target_compile_definitions(neochat PUBLIC -DHAVE_ICU)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
endif()
else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin chatbarplugin)
target_link_libraries(neochat PUBLIC
Qt::Core
Qt::Quick
Qt::Qml
Qt::Gui
Qt::Multimedia
Qt::Network
Qt::QuickControls2
KF6::I18n
KF6::Kirigami
KF6::Notifications
KF6::ConfigCore
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::IconThemes
KF6::ColorScheme
KF6::ItemModels
QuotientQt6
cmark::cmark
QCoro::Core
QCoro::Network
)
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)
if(NEOCHAT_FLATPAK)
target_compile_definitions(neochat PUBLIC NEOCHAT_FLATPAK)
endif()
if(ANDROID)
target_sources(neochat PRIVATE notifyrc.qrc)
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
if(SQLite3_FOUND)
target_link_libraries(neochat-app PRIVATE SQLite::SQLite3)
endif()
target_sources(neochat-app PRIVATE notifyrc.qrc)
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
kirigami_package_breeze_icons(ICONS
"arrow-down-symbolic"
"arrow-up-symbolic"
"arrow-up-double-symbolic"
"arrow-left-symbolic"
"arrow-right-symbolic"
"checkmark"
"help-about"
"im-user"
"im-invisible-user"
"im-kick-user"
"mail-attachment"
"dialog-cancel"
"preferences-desktop-emoticons"
"preferences-security"
"document-open"
"document-save"
"document-send"
"dialog-close"
"edit-delete-remove"
"code-context"
"document-edit"
"list-user-add"
"list-add-user"
"user-others"
"media-playback-pause"
"media-playback-start"
"media-playback-stop"
"go-previous"
"go-up"
"go-down"
"list-add"
"irc-join-channel"
"settings-configure"
"configure"
"rating"
"rating-unrated"
"search"
"mail-replied-symbolic"
"edit-clear"
"edit-copy"
"gtk-quit"
"compass"
"computer"
"network-connect"
"list-remove-user"
"org.kde.neochat"
"org.kde.neochat.tray"
"preferences-system-users"
"preferences-desktop-theme-global"
"notifications"
"notifications-disabled"
"audio-volume-high"
"audio-volume-muted"
"draw-highlight"
"zoom-in"
"zoom-out"
"image-rotate-left-symbolic"
"image-rotate-right-symbolic"
"channel-secure-symbolic"
"download"
"smiley"
"tools-check-spelling"
"username-copy"
"system-switch-user"
"bookmark-new"
"bookmark-remove"
"favorite"
"window-new"
"globe"
"visibility"
"home"
"preferences-desktop-notification"
"computer-symbolic"
"gps"
"system-users-symbolic"
"map-flat"
"documentinfo"
"view-list-details"
"go-previous"
"mail-forward-symbolic"
"dialog-warning-symbolic"
"object-rotate-left"
"object-rotate-right"
"add-subtitle"
"security-high"
"security-low"
"security-low-symbolic"
"kde"
"list-remove-symbolic"
"edit-delete"
"user-home-symbolic"
"pin-symbolic"
"kt-restore-defaults-symbolic"
"user-symbolic"
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets KF6::SyntaxHighlighting)
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
endif()
if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF6::DBusAddons AND NOT WIN32)
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()
if (TARGET KF6::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()
if (TARGET KUnifiedPush)
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
target_link_libraries(neochat PUBLIC KUnifiedPush)
if (NOT ANDROID)
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
endif()
endif()
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()

View File

@@ -1,370 +0,0 @@
# SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carl@carlschwan.eu>
# SPDX-FileCopyrightText: 2020-2021 Nicolas Fella <nicolas.fella@gmx.de>
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
add_library(neochat STATIC
controller.cpp
controller.h
roommanager.cpp
roommanager.h
models/userfiltermodel.cpp
models/userfiltermodel.h
models/userdirectorylistmodel.cpp
models/userdirectorylistmodel.h
notificationsmanager.cpp
notificationsmanager.h
blurhash.cpp
blurhash.h
blurhashimageprovider.cpp
blurhashimageprovider.h
windowcontroller.cpp
windowcontroller.h
models/serverlistmodel.cpp
models/serverlistmodel.h
logger.cpp
logger.h
models/notificationsmodel.cpp
models/notificationsmodel.h
proxycontroller.cpp
proxycontroller.h
mediamanager.cpp
mediamanager.h
sharehandler.cpp
sharehandler.h
foreigntypes.h
identityserverhelper.cpp
identityserverhelper.h
models/commonroomsmodel.cpp
models/commonroomsmodel.h
texttospeechhelper.h
texttospeechhelper.cpp
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
QT_QML_SINGLETON_TYPE TRUE
)
if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction
)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES
qml/Main.qml
qml/AccountMenu.qml
qml/CollapsedRoomDelegate.qml
qml/RoomPage.qml
qml/ExploreRoomsPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/ImageEditorPage.qml
qml/NeochatMaximizeComponent.qml
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/AttachmentPane.qml
qml/QuickFormatBar.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
qml/VerificationMessage.qml
qml/EmojiItem.qml
qml/EmojiRow.qml
qml/EmojiSas.qml
qml/VerificationCanceled.qml
qml/MessageSourceSheet.qml
qml/RoomSearchPage.qml
qml/RoomPinnedMessagesPage.qml
qml/LocationChooser.qml
qml/InvitationView.qml
qml/AvatarTabButton.qml
qml/OsmLocationPlugin.qml
qml/FullScreenMap.qml
qml/LocationsPage.qml
qml/LocationMapItem.qml
qml/RoomDrawer.qml
qml/RoomDrawerPage.qml
qml/DirectChatDrawerHeader.qml
qml/GroupChatDrawerHeader.qml
qml/RoomInformation.qml
qml/RoomMedia.qml
qml/ChooseRoomDialog.qml
qml/RemoveChildDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/NotificationsView.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
qml/UserSearchPage.qml
qml/ManualUserDialog.qml
qml/RecommendedSpaceDialog.qml
qml/ShareDialog.qml
qml/UnlockSSSSDialog.qml
qml/QrScannerPage.qml
qml/JoinRoomDialog.qml
qml/ConfirmUrlDialog.qml
qml/AccountSwitchDialog.qml
qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml
qml/EditStateDialog.qml
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
qml/AvatarNotification.qml
qml/ReasonDialog.qml
qml/NewPollDialog.qml
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat.libneochat
org.kde.neochat.rooms
org.kde.neochat.timeline
org.kde.neochat.spaces
org.kde.neochat.settings
org.kde.neochat.devtools
org.kde.neochat.login
org.kde.neochat.chatbar
)
if(NOT ANDROID AND NOT WIN32)
qt_target_qml_sources(neochat QML_FILES
qml/ShareAction.qml
qml/GlobalMenu.qml
qml/EditMenu.qml
)
else()
qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
endif()
if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
add_executable(neochat-app
main.cpp
)
if(TARGET Qt::WebView)
target_link_libraries(neochat-app PUBLIC Qt::WebView)
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
endif()
target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat-app PRIVATE
neochat
)
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
if(NOT ANDROID)
if (NOT WIN32 AND NOT APPLE)
target_sources(neochat PRIVATE trayicon_sni.cpp trayicon_sni.h)
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
else()
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
endif()
target_link_libraries(neochat PUBLIC KF6::WindowSystem)
target_compile_definitions(neochat PUBLIC -DHAVE_WINDOWSYSTEM)
endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
endif()
else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC
LibNeoChat
Timeline
Settings
Qt::Core
Qt::Quick
Qt::Qml
Qt::Gui
Qt::Multimedia
Qt::Network
Qt::QuickControls2
Qt::TextToSpeech
KF6::I18n
KF6::Kirigami
KF6::Notifications
KF6::ConfigCore
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::IconThemes
KF6::ItemModels
QuotientQt6
Login
Rooms
Spaces
)
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)
if(NEOCHAT_FLATPAK)
target_compile_definitions(neochat PUBLIC NEOCHAT_FLATPAK)
endif()
if(ANDROID)
target_sources(neochat PRIVATE notifyrc.qrc)
target_link_libraries(neochat PRIVATE Qt::Svg OpenSSL::SSL)
if(SQLite3_FOUND)
target_link_libraries(neochat-app PRIVATE SQLite::SQLite3)
endif()
target_sources(neochat-app PRIVATE notifyrc.qrc)
target_link_libraries(neochat PUBLIC Qt::Svg OpenSSL::SSL)
kirigami_package_breeze_icons(ICONS
"arrow-down-symbolic"
"arrow-up-symbolic"
"arrow-up-double-symbolic"
"arrow-left-symbolic"
"arrow-right-symbolic"
"checkmark"
"help-about"
"im-user"
"im-invisible-user"
"im-kick-user"
"mail-attachment"
"dialog-cancel"
"preferences-desktop-emoticons"
"preferences-security"
"document-open"
"document-save"
"document-send"
"dialog-close"
"edit-delete-remove"
"code-context"
"document-edit"
"list-user-add"
"list-add-user"
"user-others"
"media-playback-pause"
"media-playback-start"
"media-playback-stop"
"go-previous"
"go-up"
"go-down"
"list-add"
"irc-join-channel"
"settings-configure"
"configure"
"rating"
"rating-unrated"
"search"
"mail-replied-symbolic"
"edit-clear"
"edit-copy"
"gtk-quit"
"compass"
"computer"
"network-connect"
"list-remove-user"
"org.kde.neochat"
"org.kde.neochat.tray"
"preferences-system-users"
"preferences-desktop-theme-global"
"notifications"
"notifications-disabled"
"audio-volume-high"
"audio-volume-muted"
"draw-highlight"
"zoom-in"
"zoom-out"
"image-rotate-left-symbolic"
"image-rotate-right-symbolic"
"channel-secure-symbolic"
"download"
"smiley"
"tools-check-spelling"
"username-copy"
"system-switch-user"
"bookmark-new"
"bookmark-remove"
"favorite"
"window-new"
"globe"
"visibility"
"home"
"preferences-desktop-notification"
"computer-symbolic"
"gps"
"system-users-symbolic"
"map-flat"
"documentinfo"
"view-list-details"
"go-previous"
"mail-forward-symbolic"
"dialog-warning-symbolic"
"object-rotate-left"
"object-rotate-right"
"add-subtitle"
"security-high"
"security-low"
"security-low-symbolic"
"kde"
"list-remove-symbolic"
"edit-delete"
"user-home-symbolic"
"pin-symbolic"
"kt-restore-defaults-symbolic"
"user-symbolic"
"mark-location-symbolic"
${KIRIGAMI_ADDONS_ICONS}
)
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else()
target_link_libraries(neochat PUBLIC Qt::Widgets KF6::KIOWidgets)
install(FILES neochat.notifyrc DESTINATION ${KDE_INSTALL_KNOTIFYRCDIR})
endif()
if(NOT ANDROID)
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
endif()
if(TARGET KF6::DBusAddons AND NOT WIN32)
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
endif()
if (TARGET KF6::KIOWidgets)
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
endif()
if (TARGET KUnifiedPush)
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
target_link_libraries(neochat PUBLIC KUnifiedPush)
if (NOT ANDROID)
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
endif()
endif()
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()

View File

@@ -1,407 +0,0 @@
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-3.0-only
#include "controller.h"
#include <Quotient/connection.h>
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
#include <QGuiApplication>
#include <QTimer>
#include <signal.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/events/roommemberevent.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "accountmanager.h"
#include "enums/roomsortparameter.h"
#include "mediasizehelper.h"
#include "models/actionsmodel.h"
#include "models/messagemodel.h"
#include "models/pushrulemodel.h"
#include "models/roomlistmodel.h"
#include "models/roomtreemodel.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
#elif !defined(Q_OS_ANDROID)
#include "trayicon_sni.h"
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#endif
#endif
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
using namespace Quotient;
static std::function<bool(const Quotient::RoomEvent *)> hiddenEventFilter = [](const RoomEvent *event) -> bool {
if (event->isStateEvent() && !NeoChatConfig::showStateEvent()) {
return true;
}
if (auto roomMemberEvent = eventCast<const RoomMemberEvent>(event)) {
if ((roomMemberEvent->isJoin() || roomMemberEvent->isLeave()) && !NeoChatConfig::showLeaveJoinEvent()) {
return true;
} else if (roomMemberEvent->isRename() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::showRename()) {
return true;
} else if (roomMemberEvent->isAvatarUpdate() && !roomMemberEvent->isJoin() && !roomMemberEvent->isLeave() && !NeoChatConfig::showAvatarUpdate()) {
return true;
}
}
return false;
};
Controller::Controller(QObject *parent)
: QObject(parent)
{
Connection::setRoomType<NeoChatRoom>();
Connection::setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
connect(NeoChatConfig::self(), &NeoChatConfig::PreferUsingEncryptionChanged, this, [] {
Connection::setDirectChatEncryptionDefault(NeoChatConfig::preferUsingEncryption());
});
NeoChatConnection::setGlobalUrlPreviewDefault(NeoChatConfig::showLinkPreview());
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this] {
NeoChatConnection::setGlobalUrlPreviewDefault(NeoChatConfig::showLinkPreview());
Q_EMIT globalUrlPreviewDefaultChanged();
});
NeoChatConnection::setKeywordPushRuleDefault(static_cast<PushRuleAction::Action>(NeoChatConfig::keywordPushRuleDefault()));
connect(NeoChatConfig::self(), &NeoChatConfig::KeywordPushRuleDefaultChanged, this, [] {
NeoChatConnection::setKeywordPushRuleDefault(static_cast<PushRuleAction::Action>(NeoChatConfig::keywordPushRuleDefault()));
});
ActionsModel::setAllowQuickEdit(NeoChatConfig::allowQuickEdit());
connect(NeoChatConfig::self(), &NeoChatConfig::AllowQuickEditChanged, this, []() {
ActionsModel::setAllowQuickEdit(NeoChatConfig::allowQuickEdit());
});
MessageModel::setHiddenFilter(hiddenEventFilter);
RoomListModel::setHiddenFilter(hiddenEventFilter);
RoomTreeModel::setHiddenFilter(hiddenEventFilter);
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
connect(NeoChatConfig::self(), &NeoChatConfig::MediaMaxWidthChanged, this, []() {
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
});
connect(NeoChatConfig::self(), &NeoChatConfig::MediaMaxHeightChanged, this, []() {
MediaSizeHelper::setMaxSize(NeoChatConfig::mediaMaxWidth(), NeoChatConfig::mediaMaxHeight());
});
RoomSortParameter::setSortOrder(static_cast<RoomSortOrder::Order>(NeoChatConfig::sortOrder()));
connect(NeoChatConfig::self(), &NeoChatConfig::SortOrderChanged, this, []() {
RoomSortParameter::setSortOrder(static_cast<RoomSortOrder::Order>(NeoChatConfig::sortOrder()));
});
QList<RoomSortParameter::Parameter> configParamList;
const auto intList = NeoChatConfig::customSortOrder();
std::transform(intList.constBegin(), intList.constEnd(), std::back_inserter(configParamList), [](int param) {
return static_cast<RoomSortParameter::Parameter>(param);
});
RoomSortParameter::setCustomSortOrder(configParamList);
connect(NeoChatConfig::self(), &NeoChatConfig::CustomSortOrderChanged, this, []() {
QList<RoomSortParameter::Parameter> configParamList;
const auto intList = NeoChatConfig::customSortOrder();
std::transform(intList.constBegin(), intList.constEnd(), std::back_inserter(configParamList), [](int param) {
return static_cast<RoomSortParameter::Parameter>(param);
});
RoomSortParameter::setCustomSortOrder(configParamList);
});
ProxyController::instance().setApplicationProxy();
#ifndef Q_OS_ANDROID
setQuitOnLastWindowClosed();
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
#endif
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
delete m_trayIcon;
NeoChatConfig::self()->save();
});
#ifndef Q_OS_WINDOWS
const auto unixExitHandler = [](int) -> void {
QCoreApplication::quit();
};
const int quitSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
sigset_t blockingMask;
sigemptyset(&blockingMask);
for (const auto sig : quitSignals) {
sigaddset(&blockingMask, sig);
}
struct sigaction sa;
sa.sa_handler = unixExitHandler;
sa.sa_mask = blockingMask;
sa.sa_flags = 0;
for (auto sig : quitSignals) {
sigaction(sig, &sa, nullptr);
}
#endif
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s);
connect(connector, &KUnifiedPush::Connector::endpointChanged, this, [this](const QString &endpoint) {
if (!m_accountManager) {
return;
}
m_endpoint = endpoint;
for (auto &quotientConnection : m_accountManager->accounts()->accounts()) {
auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
connection->setupPushNotifications(endpoint);
}
});
connector->registerClient(
i18nc("The reason for using push notifications, as in: '[Push notifications are used for] Receiving notifications for new messages'",
"Receiving notifications for new messages"));
#endif
}
Controller &Controller::instance()
{
static Controller _instance;
return _instance;
}
void Controller::setAccountManager(AccountManager *manager)
{
if (manager == m_accountManager) {
return;
}
if (m_accountManager) {
m_accountManager->disconnect(this);
}
m_accountManager = manager;
if (m_accountManager) {
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
}
}
void Controller::initConnection(NeoChatConnection *connection)
{
if (!connection) {
return;
}
connect(
connection,
&NeoChatConnection::syncDone,
this,
[this, connection] {
if (!m_endpoint.isEmpty()) {
connection->setupPushNotifications(m_endpoint);
}
},
Qt::SingleShotConnection);
connect(connection, &NeoChatConnection::syncDone, this, [this, connection]() {
m_notificationsManager.handleNotifications(connection);
});
connect(this, &Controller::globalUrlPreviewDefaultChanged, connection, &NeoChatConnection::globalUrlPreviewEnabledChanged);
Q_EMIT connectionAdded(connection);
}
void Controller::teardownConnection(NeoChatConnection *connection)
{
if (!connection) {
return;
}
connection->disconnect(this);
Q_EMIT connectionDropped(connection);
}
void Controller::initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection)
{
if (oldConnection) {
oldConnection->disconnect(this);
}
if (newConnection) {
connect(newConnection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
connect(newConnection, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
newConnection->refreshBadgeNotificationCount();
}
Q_EMIT activeConnectionChanged(newConnection);
}
bool Controller::supportSystemTray() const
{
#ifdef Q_OS_ANDROID
return false;
#else
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
return de != u"GNOME"_s && de != u"Pantheon"_s;
#endif
}
void Controller::setQuitOnLastWindowClosed()
{
#ifndef Q_OS_ANDROID
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this);
m_trayIcon->show();
} else {
if (m_trayIcon) {
delete m_trayIcon;
m_trayIcon = nullptr;
}
}
#endif
}
NeoChatConnection *Controller::activeConnection() const
{
if (!m_accountManager) {
return nullptr;
}
return m_accountManager->activeConnection();
}
void Controller::setActiveConnection(NeoChatConnection *connection)
{
if (!m_accountManager) {
return;
}
m_accountManager->setActiveConnection(connection);
}
QStringList Controller::accountsLoading() const
{
if (!m_accountManager) {
return {};
}
return m_accountManager->accountsLoading();
}
void Controller::listenForNotifications()
{
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s);
auto timer = new QTimer();
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
instance().m_notificationsManager.postPushNotification(data);
timer->stop();
});
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
// Otherwise, messageReceived is never activated, and this daemon could stick around forever.
timer->start(5000);
connector->registerClient(i18n("Receiving push notifications"));
#endif
}
void Controller::clearInvitationNotification(const QString &roomId)
{
m_notificationsManager.clearInvitationNotification(roomId);
}
void Controller::updateBadgeNotificationCount(int count)
{
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
// copied from Telegram desktop
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
// Gnome requires that count is a 64bit integer
const qint64 counterSlice = std::min(count, 9999);
QVariantMap dbusUnityProperties;
if (counterSlice > 0) {
dbusUnityProperties["count"_L1] = counterSlice;
dbusUnityProperties["count-visible"_L1] = true;
} else {
dbusUnityProperties["count-visible"_L1] = false;
}
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
signal.setArguments({launcherUrl, dbusUnityProperties});
QDBusConnection::sessionBus().send(signal);
#endif // Q_OS_ANDROID
#else
qGuiApp->setBadgeNumber(count);
#endif // QT_VERSION_CHECK(6, 6, 0)
}
bool Controller::isFlatpak() const
{
#ifdef NEOCHAT_FLATPAK
return true;
#else
return false;
#endif
}
AccountRegistry *Controller::accounts()
{
return m_accountManager->accounts();
}
QString Controller::loadFileContent(const QString &path) const
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QFile::ReadOnly);
return QString::fromLatin1(file.readAll());
}
void Controller::removeConnection(const QString &userId)
{
m_accountManager->dropConnection(userId);
}
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"

View File

@@ -1,183 +0,0 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
ColumnLayout {
id: root
required property NeoChatRoom currentRoom
readonly property var invitingMember: currentRoom.member(currentRoom.invitingUserId)
readonly property string inviteTimestamp: root.currentRoom.inviteTimestamp.toLocaleString(Qt.locale(), Locale.ShortFormat)
spacing: Kirigami.Units.smallSpacing
Item {
Layout.fillHeight: true
}
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
name: root.invitingMember.displayName
source: root.invitingMember.avatarUrl
color: root.invitingMember.color
}
Loader {
active: !root.currentRoom.isDirectChat()
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
sourceComponent: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
QQC2.Label {
text: i18nc("@info:label 'Username' has invited you to this room at 'timestamp'.", "%1 has invited you to this room at %2.", root.invitingMember.displayName, root.inviteTimestamp)
Layout.alignment: Qt.AlignHCenter
}
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
}
}
Loader {
active: root.currentRoom.isDirectChat()
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
sourceComponent: ColumnLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
QQC2.Label {
text: i18nc("@info:label This user invited you to chat at 'timestamp'", "This user invited you to chat at %1.", root.inviteTimestamp)
Layout.alignment: Qt.AlignHCenter
}
}
}
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing * 4
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: acceptInviteDelegate
icon.name: "dialog-ok-symbolic"
text: i18nc("@action:button Accept this invite", "Accept Invite")
focus: true
onClicked: root.currentRoom.acceptInvitation()
}
FormCard.FormDelegateSeparator {
above: acceptInviteDelegate
below: rejectInviteDelegate
}
FormCard.FormButtonDelegate {
id: rejectInviteDelegate
icon.name: "dialog-cancel-symbolic"
text: i18nc("@action:button Reject this invite", "Reject Invite")
onClicked: RoomManager.leaveRoom(root.currentRoom)
}
}
FormCard.FormCard {
id: blockUserCard
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.fillWidth: true
FormCard.FormButtonDelegate {
icon.name: "list-remove-symbolic"
text: i18nc("@action:button Block the user", "Block %1", root.invitingMember.displayName)
onClicked: {
RoomManager.leaveRoom(root.currentRoom);
root.currentRoom.connection.addToIgnoredUsers(root.currentRoom.invitingUserId);
}
}
}
RowLayout {
visible: root.currentRoom.connection.canCheckMutualRooms
spacing: 0
Layout.topMargin: Kirigami.Units.largeSpacing * 2
Layout.fillWidth: true
Item {
Layout.fillWidth: true
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
Layout.fillWidth: true
Item {
Layout.fillWidth: true
}
Kirigami.Icon {
source: "help-hint-symbolic"
color: Kirigami.Theme.disabledTextColor
Layout.preferredWidth: Kirigami.Units.iconSizes.small
Layout.preferredHeight: Kirigami.Units.iconSizes.small
}
QQC2.Label {
color: Kirigami.Theme.disabledTextColor
text: i18nc("@info:label", "You can reject invitations from unknown users under Security settings.")
wrapMode: Text.WordWrap
// + 5 to prevent it from wrapping unnecessarily
Layout.maximumWidth: implicitWidth + 5
Layout.fillWidth: true
}
Item {
Layout.fillWidth: true
}
}
Item {
Layout.fillWidth: true
}
}
Item {
Layout.fillHeight: true
}
}

View File

@@ -1,162 +0,0 @@
// SPDX-FileCopyrightText: 2025 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
import org.kde.kirigamiaddons.delegates as Delegates
import Quotient
import org.kde.neochat
QQC2.Dialog {
id: root
required property NeoChatRoom room
title: i18nc("@title: create new poll in the room", "Create Poll")
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
contentItem: ColumnLayout {
spacing: 0
FormCard.FormComboBoxDelegate {
id: pollTypeCombo
text: i18n("Poll type:")
currentIndex: 0
textRole: "text"
valueRole: "value"
model: [
{ value: PollKind.Disclosed, text: i18n("Open poll") },
{ value: PollKind.Undisclosed, text: i18n("Closed poll") }
]
}
FormCard.FormTextDelegate {
verticalPadding: 0
text: pollTypeCombo.currentValue == 0 ? i18n("Voters can see the result as soon as they have voted") : i18n("Results are revealed only after the poll has closed")
}
FormCard.FormTextFieldDelegate {
id: questionTextField
label: i18n("Question:")
}
Repeater {
id: optionRepeater
model: ListModel {
id: optionModel
readonly property bool allValuesSet: {
for( var i = 0; i < optionModel.rowCount(); i++ ) {
if (optionModel.get(i).optionText.length <= 0) {
return false;
}
}
return true;
}
ListElement {
optionText: ""
}
ListElement {
optionText: ""
}
function values() {
let textValues = []
for( var i = 0; i < optionModel.rowCount(); i++ ) {
textValues.push(optionModel.get(i).optionText);
}
return textValues;
}
}
delegate: FormCard.AbstractFormDelegate {
id: optionDelegate
required property int index
required property string optionText
contentItem: ColumnLayout {
QQC2.Label {
id: optionLabel
Layout.fillWidth: true
text: i18nc("As in first answer option to the poll", "Option %1:", optionDelegate.index + 1)
elide: Text.ElideRight
wrapMode: Text.Wrap
Accessible.ignored: true
}
RowLayout {
Layout.fillWidth: true
QQC2.TextField {
id: textField
Layout.fillWidth: true
Accessible.name: optionLabel.text
onTextChanged: {
optionModel.set(optionDelegate.index, {optionText: text})
optionModel.allValuesSetChanged()
}
placeholderText: i18n("Enter option")
}
QQC2.ToolButton {
display: QQC2.AbstractButton.IconOnly
action: Kirigami.Action {
id: removeOptionAction
text: i18nc("@action:button", "Remove option")
icon.name: "edit-delete-remove"
onTriggered: optionModel.remove(optionDelegate.index)
}
QQC2.ToolTip {
text: removeOptionAction.text
delay: Kirigami.Units.toolTipDelay
}
}
}
}
background: null
}
}
Delegates.RoundedItemDelegate {
Layout.fillWidth: true
horizontalPadding: Kirigami.Units.largeSpacing * 2
leftInset: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
rightInset: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
highlighted: true
icon.name: "list-add"
text: i18nc("@action:button", "Add option")
onClicked: optionModel.append({optionText: ""})
}
}
footer: QQC2.DialogButtonBox {
standardButtons: QQC2.Dialog.Cancel
QQC2.Button {
enabled: optionModel.allValuesSet && questionTextField.text.length > 0
text: i18nc("@action:button", "Send")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
icon.name: "document-send"
onClicked: {
root.room.postPoll(pollTypeCombo.currentValue, questionTextField.text, optionModel.values())
root.close()
}
}
}
}

View File

@@ -1,15 +0,0 @@
// SPDX-FileCopyrightText: 2025 Ritchie Frodomar <ritchie@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "texttospeechhelper.h"
void TextToSpeechHelper::speak(const QString &textToSpeak)
{
if (!m_speech) {
m_speech = new QTextToSpeech();
}
m_speech->say(textToSpeak);
}
#include "moc_texttospeechhelper.cpp"

View File

@@ -1,21 +0,0 @@
// SPDX-FileCopyrightText: 2025 Ritchie Frodomar <ritchie@kde.org>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <QtTextToSpeech>
class TextToSpeechHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
public:
Q_INVOKABLE void speak(const QString &textToSpeak);
private:
QTextToSpeech *m_speech = nullptr;
};

View File

@@ -1,8 +1,8 @@
# SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org> # SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
qt_add_library(Chatbar STATIC) qt_add_library(chatbar STATIC)
ecm_add_qml_module(Chatbar GENERATE_PLUGIN_SOURCE ecm_add_qml_module(chatbar GENERATE_PLUGIN_SOURCE
URI org.kde.neochat.chatbar URI org.kde.neochat.chatbar
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/chatbar OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/chatbar
QML_FILES QML_FILES

View File

@@ -8,9 +8,7 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.neochat import org.kde.neochat
import org.kde.neochat.libneochat as LibNeoChat
/** /**
* @brief A component for typing and sending chat messages. * @brief A component for typing and sending chat messages.
@@ -103,7 +101,7 @@ QQC2.Control {
}, },
Kirigami.Action { Kirigami.Action {
id: mapButton id: mapButton
icon.name: "mark-location-symbolic" icon.name: "globe"
property bool isBusy: false property bool isBusy: false
text: i18n("Send a Location") text: i18n("Send a Location")
displayHint: QQC2.AbstractButton.IconOnly displayHint: QQC2.AbstractButton.IconOnly
@@ -115,20 +113,6 @@ QQC2.Control {
} }
tooltip: text tooltip: text
}, },
Kirigami.Action {
id: pollButton
icon.name: "amarok_playcount"
property bool isBusy: false
text: i18nc("@action:button", "Create a Poll")
displayHint: QQC2.AbstractButton.IconOnly
onTriggered: {
newPollDialog.createObject(root.QQC2.Overlay.overlay, {
room: root.currentRoom
}).open();
}
tooltip: text
},
Kirigami.Action { Kirigami.Action {
id: sendAction id: sendAction
@@ -167,7 +151,7 @@ QQC2.Control {
} }
leftPadding: rightPadding leftPadding: rightPadding
rightPadding: (root.width - chatBarSizeHelper.availableWidth) / 2 rightPadding: (root.width - chatBarSizeHelper.currentWidth) / 2
topPadding: 0 topPadding: 0
bottomPadding: 0 bottomPadding: 0
@@ -179,26 +163,15 @@ QQC2.Control {
Layout.fillWidth: true Layout.fillWidth: true
} }
Loader { Loader {
id: replyLoader id: paneLoader
Layout.fillWidth: true Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing Layout.margins: Kirigami.Units.largeSpacing
Layout.preferredHeight: active ? item.implicitHeight : 0 Layout.preferredHeight: active ? item.implicitHeight : 0
active: visible active: visible
visible: root.currentRoom.mainCache.replyId.length > 0 visible: root.currentRoom.mainCache.replyId.length > 0 || root.currentRoom.mainCache.attachmentPath.length > 0
sourceComponent: replyPane sourceComponent: root.currentRoom.mainCache.replyId.length > 0 ? replyPane : attachmentPane
}
Loader {
id: attachLoader
Layout.fillWidth: true
Layout.margins: Kirigami.Units.largeSpacing
Layout.preferredHeight: active ? item.implicitHeight : 0
active: visible
visible: root.currentRoom.mainCache.attachmentPath.length > 0
sourceComponent: attachmentPane
} }
RowLayout { RowLayout {
QQC2.ScrollView { QQC2.ScrollView {
@@ -310,12 +283,12 @@ QQC2.Control {
if (quickFormatBar.visible && selectedText.length > 0) { if (quickFormatBar.visible && selectedText.length > 0) {
quickFormatBar.close(); quickFormatBar.close();
} }
} else if (event.key === Qt.Key_Escape && completionMenu.visible) {
completionMenu.close();
} }
} }
Keys.onShortcutOverride: event => { Keys.onShortcutOverride: event => {
if ((_private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0) && event.key === Qt.Key_Escape) { if (completionMenu.visible) {
completionMenu.close();
} else if ((_private.chatBarCache.isReplying || _private.chatBarCache.attachmentPath.length > 0) && event.key === Qt.Key_Escape) {
_private.chatBarCache.attachmentPath = ""; _private.chatBarCache.attachmentPath = "";
_private.chatBarCache.replyId = ""; _private.chatBarCache.replyId = "";
_private.chatBarCache.threadId = ""; _private.chatBarCache.threadId = "";
@@ -355,14 +328,15 @@ QQC2.Control {
} }
} }
} }
LibNeoChat.DelegateSizeHelper { DelegateSizeHelper {
id: chatBarSizeHelper id: chatBarSizeHelper
parentItem: root
startBreakpoint: Kirigami.Units.gridUnit * 46 startBreakpoint: Kirigami.Units.gridUnit * 46
endBreakpoint: Kirigami.Units.gridUnit * 66 endBreakpoint: Kirigami.Units.gridUnit * 66
startPercentWidth: 100 startPercentWidth: 100
endPercentWidth: NeoChatConfig.compactLayout ? 100 : 85 endPercentWidth: NeoChatConfig.compactLayout ? 100 : 85
maxWidth: NeoChatConfig.compactLayout ? root.width - Kirigami.Units.largeSpacing * 2 : Kirigami.Units.gridUnit * 60 maxWidth: NeoChatConfig.compactLayout ? -1 : Kirigami.Units.gridUnit * 60
parentWidth: root.width
} }
Component { Component {
@@ -374,11 +348,8 @@ QQC2.Control {
id: replyComponent id: replyComponent
replyEventId: _private.chatBarCache.replyId replyEventId: _private.chatBarCache.replyId
replyAuthor: _private.chatBarCache.relationAuthor replyAuthor: _private.chatBarCache.relationAuthor
replyContentModel: ContentProvider.contentModelForEvent(root.currentRoom, _private.chatBarCache.replyId, true) replyContentModel: _private.chatBarCache.relationEventContentModel
Message.maxContentWidth: replyLoader.item.width maxContentWidth: paneLoader.item.width
// When the user replies to a message and the preview is loaded, make sure the text field is focused again
Component.onCompleted: textField.forceActiveFocus(Qt.OtherFocusReason)
} }
QQC2.Button { QQC2.Button {
id: cancelButton id: cancelButton
@@ -510,11 +481,6 @@ QQC2.Control {
LocationChooser {} LocationChooser {}
} }
Component {
id: newPollDialog
NewPollDialog {}
}
CompletionMenu { CompletionMenu {
id: completionMenu id: completionMenu
chatDocumentHandler: documentHandler chatDocumentHandler: documentHandler

View File

@@ -27,7 +27,6 @@ QQC2.Popup {
Component.onCompleted: { Component.onCompleted: {
chatDocumentHandler.completionModel.roomListModel = RoomManager.roomListModel; chatDocumentHandler.completionModel.roomListModel = RoomManager.roomListModel;
chatDocumentHandler.completionModel.userListModel = RoomManager.userListModel;
} }
function incrementIndex() { function incrementIndex() {

View File

@@ -19,7 +19,6 @@ QQC2.Popup {
property bool includeCustom: false property bool includeCustom: false
property bool closeOnChosen: true property bool closeOnChosen: true
property bool showQuickReaction: false property bool showQuickReaction: false
property bool showStickers: true
signal chosen(string emoji) signal chosen(string emoji)
@@ -72,7 +71,6 @@ QQC2.Popup {
currentRoom: root.currentRoom currentRoom: root.currentRoom
includeCustom: root.includeCustom includeCustom: root.includeCustom
showQuickReaction: root.showQuickReaction showQuickReaction: root.showQuickReaction
showStickers: root.showStickers
onChosen: emoji => { onChosen: emoji => {
root.chosen(emoji); root.chosen(emoji);
if (root.closeOnChosen) { if (root.closeOnChosen) {

View File

@@ -17,7 +17,6 @@ ColumnLayout {
property bool includeCustom: false property bool includeCustom: false
property bool showQuickReaction: false property bool showQuickReaction: false
property bool showStickers: true
readonly property var currentEmojiModel: { readonly property var currentEmojiModel: {
if (includeCustom) { if (includeCustom) {
@@ -44,7 +43,6 @@ ColumnLayout {
id: types id: types
Layout.fillWidth: true Layout.fillWidth: true
Kirigami.Theme.colorSet: Kirigami.Theme.View Kirigami.Theme.colorSet: Kirigami.Theme.View
visible: root.showStickers
background: null background: null
actions: [ actions: [

View File

@@ -5,6 +5,7 @@
#include <Quotient/roommember.h> #include <Quotient/roommember.h>
#include "chatdocumenthandler.h"
#include "eventhandler.h" #include "eventhandler.h"
#include "models/actionsmodel.h" #include "models/actionsmodel.h"
#include "neochatroom.h" #include "neochatroom.h"
@@ -87,6 +88,7 @@ void ChatBarCache::setReplyId(const QString &replyId)
m_relationType = Reply; m_relationType = Reply;
} }
m_attachmentPath = QString(); m_attachmentPath = QString();
delete m_relationContentModel;
Q_EMIT relationIdChanged(oldEventId, m_relationId); Q_EMIT relationIdChanged(oldEventId, m_relationId);
Q_EMIT attachmentPathChanged(); Q_EMIT attachmentPathChanged();
} }
@@ -116,6 +118,7 @@ void ChatBarCache::setEditId(const QString &editId)
m_relationType = Edit; m_relationType = Edit;
} }
m_attachmentPath = QString(); m_attachmentPath = QString();
delete m_relationContentModel;
Q_EMIT relationIdChanged(oldEventId, m_relationId); Q_EMIT relationIdChanged(oldEventId, m_relationId);
Q_EMIT attachmentPathChanged(); Q_EMIT attachmentPathChanged();
} }
@@ -158,6 +161,28 @@ QString ChatBarCache::relationMessage() const
return {}; return {};
} }
MessageContentModel *ChatBarCache::relationEventContentModel()
{
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return nullptr;
}
if (m_relationId.isEmpty()) {
return nullptr;
}
if (m_relationContentModel != nullptr) {
return m_relationContentModel;
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return nullptr;
}
m_relationContentModel = new MessageContentModel(room, m_relationId, true);
return m_relationContentModel;
}
bool ChatBarCache::isThreaded() const bool ChatBarCache::isThreaded() const
{ {
return !m_threadId.isEmpty(); return !m_threadId.isEmpty();
@@ -188,18 +213,11 @@ void ChatBarCache::setAttachmentPath(const QString &attachmentPath)
return; return;
} }
m_attachmentPath = attachmentPath; m_attachmentPath = attachmentPath;
Q_EMIT attachmentPathChanged();
#if (Quotient_VERSION_MINOR < 10 && Quotient_VERSION_PATCH < 3) || Quotient_VERSION_MINOR < 9
m_relationType = None; m_relationType = None;
const auto oldEventId = std::exchange(m_relationId, QString()); const auto oldEventId = std::exchange(m_relationId, QString());
delete m_relationContentModel;
Q_EMIT attachmentPathChanged();
Q_EMIT relationIdChanged(oldEventId, m_relationId); Q_EMIT relationIdChanged(oldEventId, m_relationId);
#else
if (m_relationType == Edit) {
const auto oldEventId = std::exchange(m_relationId, QString());
Q_EMIT relationIdChanged(oldEventId, m_relationId);
}
#endif
} }
void ChatBarCache::clearRelations() void ChatBarCache::clearRelations()
@@ -207,6 +225,7 @@ void ChatBarCache::clearRelations()
const auto oldEventId = std::exchange(m_relationId, QString()); const auto oldEventId = std::exchange(m_relationId, QString());
const auto oldThreadId = std::exchange(m_threadId, QString()); const auto oldThreadId = std::exchange(m_threadId, QString());
m_attachmentPath = QString(); m_attachmentPath = QString();
delete m_relationContentModel;
Q_EMIT relationIdChanged(oldEventId, m_relationId); Q_EMIT relationIdChanged(oldEventId, m_relationId);
Q_EMIT threadIdChanged(oldThreadId, m_threadId); Q_EMIT threadIdChanged(oldThreadId, m_threadId);
Q_EMIT attachmentPathChanged(); Q_EMIT attachmentPathChanged();
@@ -217,6 +236,54 @@ QList<Mention> *ChatBarCache::mentions()
return &m_mentions; return &m_mentions;
} }
void ChatBarCache::updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler)
{
documentHandler->setDocument(document);
if (parent() == nullptr) {
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
return;
}
if (m_relationId.isEmpty()) {
return;
}
auto room = dynamic_cast<NeoChatRoom *>(parent());
if (room == nullptr) {
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
return;
}
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
if (const auto &roomMessageEvent = &*event->viewAs<Quotient::RoomMessageEvent>()) {
// Replaces the mentions that are baked into the HTML but plaintext in the original markdown
const QRegularExpression re(uR"lit(<a\shref="https:\/\/matrix.to\/#\/([\S]*)"\s?>([\S]*)<\/a>)lit"_s);
m_mentions.clear();
int linkSize = 0;
auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent));
while (matches.hasNext()) {
const QRegularExpressionMatch match = matches.next();
if (match.hasMatch()) {
const QString id = match.captured(1);
const QString name = match.captured(2);
const int position = match.capturedStart(0) - linkSize;
const int end = position + name.length();
linkSize += match.capturedLength(0) - name.length();
QTextCursor cursor(documentHandler->document()->textDocument());
cursor.setPosition(position);
cursor.setPosition(end, QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true);
m_mentions.push_back(Mention{.cursor = cursor, .text = name, .start = position, .position = end, .id = id});
}
}
}
}
}
QString ChatBarCache::savedText() const QString ChatBarCache::savedText() const
{ {
return m_savedText; return m_savedText;
@@ -235,19 +302,8 @@ void ChatBarCache::postMessage()
return; return;
} }
bool isReply = !replyId().isEmpty();
std::optional<Quotient::EventRelation> relatesTo = std::nullopt;
if (!threadId().isEmpty()) {
relatesTo = Quotient::EventRelation::replyInThread(threadId(), !isReply, isReply ? replyId() : threadId());
} else if (!editId().isEmpty()) {
relatesTo = Quotient::EventRelation::replace(editId());
} else if (isReply) {
relatesTo = Quotient::EventRelation::replyTo(replyId());
}
if (!attachmentPath().isEmpty()) { if (!attachmentPath().isEmpty()) {
room->uploadFile(QUrl(attachmentPath()), sendText(), relatesTo); room->uploadFile(QUrl(attachmentPath()), sendText());
clearCache(); clearCache();
return; return;
} }
@@ -265,12 +321,22 @@ void ChatBarCache::postMessage()
return; return;
} }
bool isReply = !replyId().isEmpty();
const auto replyIt = room->findInTimeline(replyId()); const auto replyIt = room->findInTimeline(replyId());
if (replyIt == room->historyEdge()) { if (replyIt == room->historyEdge()) {
isReply = false; isReply = false;
} }
auto content = std::make_unique<Quotient::EventContent::TextContent>(sendText, u"text/html"_s); auto content = std::make_unique<Quotient::EventContent::TextContent>(sendText, u"text/html"_s);
std::optional<Quotient::EventRelation> relatesTo = std::nullopt;
if (!threadId().isEmpty()) {
relatesTo = Quotient::EventRelation::replyInThread(threadId(), !isReply, isReply ? replyId() : threadId());
} else if (!editId().isEmpty()) {
relatesTo = Quotient::EventRelation::replace(editId());
} else if (isReply) {
relatesTo = Quotient::EventRelation::replyTo(replyId());
}
room->post<Quotient::RoomMessageEvent>(text(), *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), std::move(content), relatesTo); room->post<Quotient::RoomMessageEvent>(text(), *std::get<std::optional<Quotient::RoomMessageEvent::MsgType>>(result), std::move(content), relatesTo);
clearCache(); clearCache();

View File

@@ -8,11 +8,16 @@
#include <QQuickTextDocument> #include <QQuickTextDocument>
#include <QTextCursor> #include <QTextCursor>
#include "models/messagecontentmodel.h"
class ChatDocumentHandler;
namespace Quotient namespace Quotient
{ {
class RoomMember; class RoomMember;
} }
/** /**
* @brief Defines a user mention in the current chat or edit text. * @brief Defines a user mention in the current chat or edit text.
*/ */
@@ -106,6 +111,13 @@ class ChatBarCache : public QObject
*/ */
Q_PROPERTY(QString relationMessage READ relationMessage NOTIFY relationIdChanged) Q_PROPERTY(QString relationMessage READ relationMessage NOTIFY relationIdChanged)
/**
* @brief The MessageContentModel for the related message.
*
* Will be nullptr if no related message.
*/
Q_PROPERTY(MessageContentModel *relationEventContentModel READ relationEventContentModel NOTIFY relationIdChanged)
/** /**
* @brief Whether the chat bar is replying in a thread. * @brief Whether the chat bar is replying in a thread.
*/ */
@@ -155,6 +167,7 @@ public:
Quotient::RoomMember relationAuthor() const; Quotient::RoomMember relationAuthor() const;
QString relationMessage() const; QString relationMessage() const;
MessageContentModel *relationEventContentModel();
bool isThreaded() const; bool isThreaded() const;
QString threadId() const; QString threadId() const;
@@ -175,6 +188,11 @@ public:
*/ */
QList<Mention> *mentions(); QList<Mention> *mentions();
/**
* @brief Update the mentions in @p document when editing a message.
*/
Q_INVOKABLE void updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler);
/** /**
* @brief Get the saved chat bar text. * @brief Get the saved chat bar text.
*/ */
@@ -207,5 +225,7 @@ private:
QList<Mention> m_mentions; QList<Mention> m_mentions;
QString m_savedText; QString m_savedText;
QPointer<MessageContentModel> m_relationContentModel;
void clearCache(); void clearCache();
}; };

View File

@@ -15,7 +15,6 @@
#include <Sonnet/Settings> #include <Sonnet/Settings>
#include "chatdocumenthandler_logging.h" #include "chatdocumenthandler_logging.h"
#include "eventhandler.h"
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
@@ -118,10 +117,6 @@ ChatDocumentHandler::ChatDocumentHandler(QObject *parent)
}); });
}); });
connect(this, &ChatDocumentHandler::documentChanged, this, [this]() { connect(this, &ChatDocumentHandler::documentChanged, this, [this]() {
if (!m_document) {
m_highlighter->setDocument(nullptr);
return;
}
m_highlighter->setDocument(m_document->textDocument()); m_highlighter->setDocument(m_document->textDocument());
}); });
connect(this, &ChatDocumentHandler::cursorPositionChanged, this, [this]() { connect(this, &ChatDocumentHandler::cursorPositionChanged, this, [this]() {
@@ -361,43 +356,4 @@ void ChatDocumentHandler::setErrorColor(const QColor &color)
Q_EMIT errorColorChanged(); Q_EMIT errorColorChanged();
} }
void ChatDocumentHandler::updateMentions(QQuickTextDocument *document, const QString &editId)
{
setDocument(document);
if (editId.isEmpty() || !m_chatBarCache || !m_room) {
return;
}
if (auto event = m_room->findInTimeline(editId); event != m_room->historyEdge()) {
if (const auto &roomMessageEvent = &*event->viewAs<Quotient::RoomMessageEvent>()) {
// Replaces the mentions that are baked into the HTML but plaintext in the original markdown
const QRegularExpression re(uR"lit(<a\shref="https:\/\/matrix.to\/#\/([\S]*)"\s?>([\S]*)<\/a>)lit"_s);
m_chatBarCache->mentions()->clear();
int linkSize = 0;
auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent));
while (matches.hasNext()) {
const QRegularExpressionMatch match = matches.next();
if (match.hasMatch()) {
const QString id = match.captured(1);
const QString name = match.captured(2);
const int position = match.capturedStart(0) - linkSize;
const int end = position + name.length();
linkSize += match.capturedLength(0) - name.length();
QTextCursor cursor(this->document()->textDocument());
cursor.setPosition(position);
cursor.setPosition(end, QTextCursor::KeepAnchor);
cursor.setKeepPositionOnInsert(true);
pushMention(Mention{.cursor = cursor, .text = name, .start = position, .position = end, .id = id});
}
}
}
}
}
#include "moc_chatdocumenthandler.cpp" #include "moc_chatdocumenthandler.cpp"

View File

@@ -141,11 +141,6 @@ public:
[[nodiscard]] QColor errorColor() const; [[nodiscard]] QColor errorColor() const;
void setErrorColor(const QColor &color); void setErrorColor(const QColor &color);
/**
* @brief Update the mentions in @p document when editing a message.
*/
Q_INVOKABLE void updateMentions(QQuickTextDocument *document, const QString &editId);
Q_SIGNALS: Q_SIGNALS:
void documentChanged(); void documentChanged();
void cursorPositionChanged(); void cursorPositionChanged();

View File

@@ -9,7 +9,6 @@
ColorSchemer::ColorSchemer(QObject *parent) ColorSchemer::ColorSchemer(QObject *parent)
: QObject(parent) : QObject(parent)
{ {
KColorSchemeManager::instance();
} }
ColorSchemer::~ColorSchemer() ColorSchemer::~ColorSchemer()

459
src/controller.cpp Normal file
View File

@@ -0,0 +1,459 @@
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
// SPDX-FileCopyrightText: 2020 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-3.0-only
#include "controller.h"
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
#include <QGuiApplication>
#include <QTimer>
#include <signal.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"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
#elif !defined(Q_OS_ANDROID)
#include "trayicon_sni.h"
#endif
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
#include <QDBusConnection>
#include <QDBusInterface>
#include <QDBusMessage>
#endif
#endif
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
bool testMode = false;
using namespace Quotient;
Controller::Controller(QObject *parent)
: QObject(parent)
{
Connection::setRoomType<NeoChatRoom>();
ProxyController::instance().setApplicationProxy();
#ifndef Q_OS_ANDROID
setQuitOnLastWindowClosed();
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
#endif
if (!testMode) {
QTimer::singleShot(0, this, [this] {
invokeLogin();
});
} else {
auto c = new NeoChatConnection(this);
c->assumeIdentity(u"@user:localhost:1234"_s, u"device_1234"_s, u"token_1234"_s);
connect(c, &Connection::connected, this, [c, this]() {
m_accountRegistry.add(c);
c->syncLoop();
});
}
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
delete m_trayIcon;
NeoChatConfig::self()->save();
});
#ifndef Q_OS_WINDOWS
const auto unixExitHandler = [](int) -> void {
QCoreApplication::quit();
};
const int quitSignals[] = {SIGQUIT, SIGINT, SIGTERM, SIGHUP};
sigset_t blockingMask;
sigemptyset(&blockingMask);
for (const auto sig : quitSignals) {
sigaddset(&blockingMask, sig);
}
struct sigaction sa;
sa.sa_handler = unixExitHandler;
sa.sa_mask = blockingMask;
sa.sa_flags = 0;
for (auto sig : quitSignals) {
sigaction(sig, &sa, nullptr);
}
#endif
static int oldAccountCount = 0;
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
if (m_accountRegistry.size() > oldAccountCount) {
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
connect(
connection,
&NeoChatConnection::syncDone,
this,
[this, connection] {
if (!m_endpoint.isEmpty()) {
connection->setupPushNotifications(m_endpoint);
}
},
Qt::SingleShotConnection);
}
oldAccountCount = m_accountRegistry.size();
});
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s);
connect(connector, &KUnifiedPush::Connector::endpointChanged, this, [this](const QString &endpoint) {
m_endpoint = endpoint;
for (auto &quotientConnection : m_accountRegistry) {
auto connection = dynamic_cast<NeoChatConnection *>(quotientConnection);
connection->setupPushNotifications(endpoint);
}
});
connector->registerClient(
i18nc("The reason for using push notifications, as in: '[Push notifications are used for] Receiving notifications for new messages'",
"Receiving notifications for new messages"));
m_endpoint = connector->endpoint();
#endif
}
Controller &Controller::instance()
{
static Controller _instance;
return _instance;
}
void Controller::addConnection(NeoChatConnection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to add a null connection");
m_accountRegistry.add(c);
c->setLazyLoading(true);
connect(c, &NeoChatConnection::syncDone, this, [c] {
c->sync(30000);
c->saveState();
});
connect(c, &NeoChatConnection::loggedOut, this, [this, c] {
if (accounts().count() > 1) {
// Only set the connection if the account being logged out is currently active
if (c == activeConnection()) {
setActiveConnection(dynamic_cast<NeoChatConnection *>(accounts().accounts()[0]));
}
} else {
setActiveConnection(nullptr);
}
dropConnection(c);
});
connect(c, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
connect(c, &NeoChatConnection::syncDone, this, [this, c]() {
m_notificationsManager.handleNotifications(c);
});
c->sync();
Q_EMIT connectionAdded(c);
}
void Controller::dropConnection(NeoChatConnection *c)
{
Q_ASSERT_X(c, __FUNCTION__, "Attempt to drop a null connection");
c->disconnect(this);
c->disconnect(&m_notificationsManager);
m_accountRegistry.drop(c);
Q_EMIT connectionDropped(c);
}
void Controller::invokeLogin()
{
const auto accounts = SettingsGroup("Accounts"_L1).childGroups();
for (const auto &accountId : accounts) {
AccountSettings account{accountId};
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
accessToken = QString::fromLatin1(accessTokenLoadingJob->binaryData());
} else {
return;
}
auto connection = new NeoChatConnection(account.homeserver());
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);
}
});
connection->assumeIdentity(account.userId(), account.deviceId(), accessToken);
});
}
}
}
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QString &userId)
{
qDebug() << "Reading access token from the keychain for" << userId;
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
job->setKey(userId);
// Handling of errors
connect(job, &QKeychain::Job::finished, this, [this, job]() {
if (job->error() == QKeychain::Error::NoError) {
return;
}
switch (job->error()) {
case QKeychain::EntryNotFound:
Q_EMIT errorOccured(i18n("Access token wasn't found: Maybe it was deleted?"));
break;
case QKeychain::AccessDeniedByUser:
case QKeychain::AccessDenied:
Q_EMIT errorOccured(i18n("Access to keychain was denied: Please allow NeoChat to read the access token"));
break;
case QKeychain::NoBackendAvailable:
Q_EMIT errorOccured(i18n("No keychain available: Please install a keychain, e.g. KWallet or GNOME keyring on Linux"));
break;
case QKeychain::OtherError:
Q_EMIT errorOccured(i18n("Unable to read access token: %1", job->errorString()));
break;
default:
break;
}
});
job->start();
return job;
}
void 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();
}
bool Controller::supportSystemTray() const
{
#ifdef Q_OS_ANDROID
return false;
#else
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
return de != u"GNOME"_s && de != u"Pantheon"_s;
#endif
}
void Controller::setQuitOnLastWindowClosed()
{
#ifndef Q_OS_ANDROID
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this);
m_trayIcon->show();
} else {
if (m_trayIcon) {
delete m_trayIcon;
m_trayIcon = nullptr;
}
}
#endif
}
NeoChatConnection *Controller::activeConnection() const
{
if (m_connection.isNull()) {
return nullptr;
}
return m_connection;
}
void Controller::setActiveConnection(NeoChatConnection *connection)
{
if (connection == m_connection) {
return;
}
if (m_connection != nullptr) {
m_connection->disconnect(this);
m_connection->disconnect(&m_notificationsManager);
}
m_connection = connection;
if (m_connection != nullptr) {
m_connection->refreshBadgeNotificationCount();
updateBadgeNotificationCount(m_connection, m_connection->badgeNotificationCount());
connect(m_connection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
}
Q_EMIT activeConnectionChanged(m_connection);
}
void Controller::listenForNotifications()
{
#ifdef HAVE_KUNIFIEDPUSH
auto connector = new KUnifiedPush::Connector(u"org.kde.neochat"_s);
auto timer = new QTimer();
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
instance().m_notificationsManager.postPushNotification(data);
timer->stop();
});
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.
// Otherwise, messageReceived is never activated, and this daemon could stick around forever.
timer->start(5000);
connector->registerClient(i18n("Receiving push notifications"));
#endif
}
void Controller::clearInvitationNotification(const QString &roomId)
{
m_notificationsManager.clearInvitationNotification(roomId);
}
void Controller::updateBadgeNotificationCount(NeoChatConnection *connection, int count)
{
if (connection == m_connection) {
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
// copied from Telegram desktop
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
// Gnome requires that count is a 64bit integer
const qint64 counterSlice = std::min(count, 9999);
QVariantMap dbusUnityProperties;
if (counterSlice > 0) {
dbusUnityProperties["count"_L1] = counterSlice;
dbusUnityProperties["count-visible"_L1] = true;
} else {
dbusUnityProperties["count-visible"_L1] = false;
}
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
signal.setArguments({launcherUrl, dbusUnityProperties});
QDBusConnection::sessionBus().send(signal);
#endif // Q_OS_ANDROID
#else
qGuiApp->setBadgeNumber(count);
#endif // QT_VERSION_CHECK(6, 6, 0)
}
}
bool Controller::isFlatpak() const
{
#ifdef NEOCHAT_FLATPAK
return true;
#else
return false;
#endif
}
AccountRegistry &Controller::accounts()
{
return m_accountRegistry;
}
QString Controller::loadFileContent(const QString &path) const
{
QUrl url(path);
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
file.open(QFile::ReadOnly);
return QString::fromLatin1(file.readAll());
}
void Controller::setTestMode(bool test)
{
testMode = test;
}
void Controller::removeConnection(const QString &userId)
{
// When loadAccessTokenFromKeyChain() fails m_connectionsLoading won't have an
// entry for it so we need to check both separately.
if (m_accountsLoading.contains(userId)) {
m_accountsLoading.removeAll(userId);
Q_EMIT accountsLoadingChanged();
}
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
auto connection = m_connectionsLoading[userId];
SettingsGroup("Accounts"_L1).remove(userId);
}
}
bool Controller::csSupported() const
{
return true;
}
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"

View File

@@ -6,7 +6,6 @@
#include <QObject> #include <QObject>
#include <QQmlEngine> #include <QQmlEngine>
#include "accountmanager.h"
#include "neochatconnection.h" #include "neochatconnection.h"
#include "notificationsmanager.h" #include "notificationsmanager.h"
#include <Quotient/accountregistry.h> #include <Quotient/accountregistry.h>
@@ -49,7 +48,9 @@ class Controller : public QObject
*/ */
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT) Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
Q_PROPERTY(QStringList accountsLoading READ accountsLoading NOTIFY accountsLoadingChanged) Q_PROPERTY(QStringList accountsLoading MEMBER m_accountsLoading NOTIFY accountsLoadingChanged)
Q_PROPERTY(bool csSupported READ csSupported CONSTANT)
public: public:
static Controller &instance(); static Controller &instance();
@@ -59,12 +60,23 @@ public:
return &instance(); return &instance();
} }
void setAccountManager(AccountManager *manager);
[[nodiscard]] NeoChatConnection *activeConnection() const;
void setActiveConnection(NeoChatConnection *connection); void setActiveConnection(NeoChatConnection *connection);
[[nodiscard]] NeoChatConnection *activeConnection() const;
QStringList accountsLoading() const; /**
* @brief Add a new connection to the account registry.
*/
void addConnection(NeoChatConnection *c);
/**
* @brief Drop a connection from the account registry.
*/
void dropConnection(NeoChatConnection *c);
/**
* @brief Save an access token to the keychain for the given account.
*/
void saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
[[nodiscard]] bool supportSystemTray() const; [[nodiscard]] bool supportSystemTray() const;
@@ -85,10 +97,14 @@ public:
Q_INVOKABLE QString loadFileContent(const QString &path) const; Q_INVOKABLE QString loadFileContent(const QString &path) const;
Quotient::AccountRegistry *accounts(); Quotient::AccountRegistry &accounts();
static void setTestMode(bool testMode);
Q_INVOKABLE void removeConnection(const QString &userId); Q_INVOKABLE void removeConnection(const QString &userId);
bool csSupported() const;
/** /**
* @brief Revert all configuration values to their default. * @brief Revert all configuration values to their default.
* *
@@ -103,22 +119,23 @@ public:
private: private:
explicit Controller(QObject *parent = nullptr); explicit Controller(QObject *parent = nullptr);
QPointer<AccountManager> m_accountManager;
void initConnection(NeoChatConnection *connection);
void teardownConnection(NeoChatConnection *connection);
void initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection);
QPointer<NeoChatConnection> m_connection; QPointer<NeoChatConnection> m_connection;
TrayIcon *m_trayIcon = nullptr; TrayIcon *m_trayIcon = nullptr;
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const QString &account);
Quotient::AccountRegistry m_accountRegistry;
QStringList m_accountsLoading;
QMap<QString, QPointer<NeoChatConnection>> m_connectionsLoading;
QString m_endpoint; QString m_endpoint;
QStringList m_shownImages; QStringList m_shownImages;
NotificationsManager m_notificationsManager; NotificationsManager m_notificationsManager;
private Q_SLOTS: private Q_SLOTS:
void invokeLogin();
void setQuitOnLastWindowClosed(); void setQuitOnLastWindowClosed();
void updateBadgeNotificationCount(int count); void updateBadgeNotificationCount(NeoChatConnection *connection, int count);
Q_SIGNALS: Q_SIGNALS:
/** /**
@@ -129,6 +146,4 @@ Q_SIGNALS:
void connectionDropped(NeoChatConnection *connection); void connectionDropped(NeoChatConnection *connection);
void activeConnectionChanged(NeoChatConnection *connection); void activeConnectionChanged(NeoChatConnection *connection);
void accountsLoadingChanged(); void accountsLoadingChanged();
void globalUrlPreviewDefaultChanged();
}; };

View File

@@ -12,56 +12,20 @@ DelegateSizeHelper::DelegateSizeHelper(QObject *parent)
{ {
} }
QQuickItem *DelegateSizeHelper::parentItem() const qreal DelegateSizeHelper::parentWidth() const
{ {
return m_parentItem; return m_parentWidth;
} }
void DelegateSizeHelper::setParentItem(QQuickItem *parentItem) void DelegateSizeHelper::setParentWidth(qreal parentWidth)
{ {
if (parentItem == m_parentItem) { if (parentWidth == m_parentWidth) {
return; return;
} }
m_parentItem = parentItem; m_parentWidth = parentWidth;
Q_EMIT parentWidthChanged();
if (m_parentItem) { Q_EMIT currentPercentageWidthChanged();
connect(m_parentItem, &QQuickItem::widthChanged, this, [this]() { Q_EMIT currentWidthChanged();
calcWidthsChanged();
});
}
Q_EMIT parentItemChanged();
calcWidthsChanged();
}
qreal DelegateSizeHelper::leftPadding() const
{
return m_leftPadding;
}
void DelegateSizeHelper::setLeftPadding(qreal leftPadding)
{
if (qFuzzyCompare(leftPadding, m_leftPadding)) {
return;
}
m_leftPadding = leftPadding;
Q_EMIT leftPaddingChanged();
calcWidthsChanged();
}
qreal DelegateSizeHelper::rightPadding() const
{
return m_rightPadding;
}
void DelegateSizeHelper::setRightPadding(qreal rightPadding)
{
if (qFuzzyCompare(rightPadding, m_rightPadding)) {
return;
}
m_rightPadding = rightPadding;
Q_EMIT rightPaddingChanged();
calcWidthsChanged();
} }
qreal DelegateSizeHelper::startBreakpoint() const qreal DelegateSizeHelper::startBreakpoint() const
@@ -76,7 +40,6 @@ void DelegateSizeHelper::setStartBreakpoint(qreal startBreakpoint)
} }
m_startBreakpoint = startBreakpoint; m_startBreakpoint = startBreakpoint;
Q_EMIT startBreakpointChanged(); Q_EMIT startBreakpointChanged();
calcWidthsChanged();
} }
qreal DelegateSizeHelper::endBreakpoint() const qreal DelegateSizeHelper::endBreakpoint() const
@@ -91,7 +54,6 @@ void DelegateSizeHelper::setEndBreakpoint(qreal endBreakpoint)
} }
m_endBreakpoint = endBreakpoint; m_endBreakpoint = endBreakpoint;
Q_EMIT endBreakpointChanged(); Q_EMIT endBreakpointChanged();
calcWidthsChanged();
} }
int DelegateSizeHelper::startPercentWidth() const int DelegateSizeHelper::startPercentWidth() const
@@ -106,7 +68,6 @@ void DelegateSizeHelper::setStartPercentWidth(int startPercentWidth)
} }
m_startPercentWidth = startPercentWidth; m_startPercentWidth = startPercentWidth;
Q_EMIT startPercentWidthChanged(); Q_EMIT startPercentWidthChanged();
calcWidthsChanged();
} }
int DelegateSizeHelper::endPercentWidth() const int DelegateSizeHelper::endPercentWidth() const
@@ -121,19 +82,11 @@ void DelegateSizeHelper::setEndPercentWidth(int endPercentWidth)
} }
m_endPercentWidth = endPercentWidth; m_endPercentWidth = endPercentWidth;
Q_EMIT endPercentWidthChanged(); Q_EMIT endPercentWidthChanged();
calcWidthsChanged();
} }
qreal DelegateSizeHelper::maxWidth() const qreal DelegateSizeHelper::maxWidth() const
{ {
if (m_maxWidth == std::nullopt) { return m_maxWidth;
if (m_parentItem) {
return m_parentItem->width();
}
return 0.0;
}
return *m_maxWidth;
} }
void DelegateSizeHelper::setMaxWidth(qreal maxWidth) void DelegateSizeHelper::setMaxWidth(qreal maxWidth)
@@ -143,26 +96,23 @@ void DelegateSizeHelper::setMaxWidth(qreal maxWidth)
} }
m_maxWidth = maxWidth; m_maxWidth = maxWidth;
Q_EMIT maxWidthChanged(); Q_EMIT maxWidthChanged();
calcWidthsChanged();
} }
qreal DelegateSizeHelper::maxAvailableWidth() const int DelegateSizeHelper::calculateCurrentPercentageWidth() const
{ {
if (!m_parentItem || qFuzzyCompare(m_parentItem->width(), 0)) { // Don't do anything if m_parentWidth hasn't been set yet.
return 0; if (m_parentWidth < 0) {
return -1;
} }
return std::max(m_parentItem->width() - m_leftPadding - m_rightPadding, 0.0);
}
int DelegateSizeHelper::calculateAvailablePercentageWidth() const
{
// Don't bother with calculations for a horizontal line. // Don't bother with calculations for a horizontal line.
if (m_startPercentWidth == m_endPercentWidth) { if (m_startPercentWidth == m_endPercentWidth) {
return m_startPercentWidth; return m_startPercentWidth;
} }
// Dividing by zero is a bad idea. // Dividing by zero is a bad idea.
if (m_startBreakpoint == m_endBreakpoint || qFuzzyCompare(maxAvailableWidth(), 0)) { if (m_startBreakpoint == m_endBreakpoint) {
return 100; qWarning() << "DelegateSizeHelper::calculateCurrentPercentageWidth() - m_startBreakpoint is equal to m_endBreakpoint this would lead to divide by "
"zero, aborting";
return -1;
} }
// Fit to y = mx + c // Fit to y = mx + c
@@ -174,35 +124,32 @@ int DelegateSizeHelper::calculateAvailablePercentageWidth() const
int maxPercentWidth = endPercentBigger ? m_endPercentWidth : m_startPercentWidth; int maxPercentWidth = endPercentBigger ? m_endPercentWidth : m_startPercentWidth;
int minPercentWidth = endPercentBigger ? m_startPercentWidth : m_endPercentWidth; int minPercentWidth = endPercentBigger ? m_startPercentWidth : m_endPercentWidth;
int calcPercentWidth = std::round(m * maxAvailableWidth() + c); int calcPercentWidth = std::round(m * m_parentWidth + c);
return std::clamp(calcPercentWidth, minPercentWidth, maxPercentWidth); return std::clamp(calcPercentWidth, minPercentWidth, maxPercentWidth);
} }
int DelegateSizeHelper::availablePercentageWidth() const int DelegateSizeHelper::currentPercentageWidth() const
{ {
return calculateAvailablePercentageWidth(); return calculateCurrentPercentageWidth();
} }
qreal DelegateSizeHelper::availableWidth() const qreal DelegateSizeHelper::currentWidth() const
{ {
qreal absoluteWidth = maxAvailableWidth() * availablePercentageWidth() * 0.01; if (m_parentWidth < 0) {
return std::round(std::min(absoluteWidth, maxWidth())); return 0.0;
} }
int percentWidth = calculateCurrentPercentageWidth();
// - 1 means bad input values so don't try to calculate.
if (percentWidth == -1) {
return 0.0;
}
qreal DelegateSizeHelper::leftX() const qreal absoluteWidth = m_parentWidth * percentWidth * 0.01;
{ if (m_maxWidth < 0.0) {
return m_leftPadding + (maxAvailableWidth() - availableWidth()) / 2; return std::round(absoluteWidth);
} } else {
return std::round(std::min(absoluteWidth, m_maxWidth));
qreal DelegateSizeHelper::rightX() const }
{
return m_leftPadding + maxAvailableWidth() - (maxAvailableWidth() - availableWidth()) / 2;
}
void DelegateSizeHelper::calcWidthsChanged()
{
Q_EMIT availablePercentageWidthChanged();
Q_EMIT availableWidthChanged();
} }
#include "moc_delegatesizehelper.cpp" #include "moc_delegatesizehelper.cpp"

View File

@@ -5,7 +5,6 @@
#include <QObject> #include <QObject>
#include <QQmlEngine> #include <QQmlEngine>
#include <QQuickItem>
/** /**
* @class DelegateSizeHelper * @class DelegateSizeHelper
@@ -28,25 +27,9 @@ class DelegateSizeHelper : public QObject
QML_ELEMENT QML_ELEMENT
/** /**
* @brief The parent item that defines the available content area. * @brief The width of the component's parent.
*/ */
Q_PROPERTY(QQuickItem *parentItem READ parentItem WRITE setParentItem NOTIFY parentItemChanged) Q_PROPERTY(qreal parentWidth READ parentWidth WRITE setParentWidth NOTIFY parentWidthChanged)
/**
* @brief The amount of padding to be removed from the left of the available content area.
*
* The padding is removed before calculating the available width, i.e. max available width
* at 100% is equal to parent width minus padding.
*/
Q_PROPERTY(qreal leftPadding READ leftPadding WRITE setLeftPadding NOTIFY leftPaddingChanged)
/**
* @brief The amount of padding to be removed from the right of the available content area.
*
* The padding is removed before calculating the available width, i.e. max available width
* at 100% is equal to parent width minus padding.
*/
Q_PROPERTY(qreal rightPadding READ rightPadding WRITE setRightPadding NOTIFY rightPaddingChanged)
/** /**
* @brief The width (in px) when the width percentage should start to transition. * @brief The width (in px) when the width percentage should start to transition.
@@ -84,7 +67,7 @@ class DelegateSizeHelper : public QObject
* *
* @sa parentWidth, startBreakpoint, endBreakpoint * @sa parentWidth, startBreakpoint, endBreakpoint
*/ */
Q_PROPERTY(int availablePercentageWidth READ availablePercentageWidth NOTIFY availableWidthChanged) Q_PROPERTY(int currentPercentageWidth READ currentPercentageWidth NOTIFY currentPercentageWidthChanged)
/** /**
* @brief The width (in px) of the component at the current parentWidth. * @brief The width (in px) of the component at the current parentWidth.
@@ -93,72 +76,50 @@ class DelegateSizeHelper : public QObject
* *
* @sa parentWidth * @sa parentWidth
*/ */
Q_PROPERTY(qreal availableWidth READ availableWidth NOTIFY availableWidthChanged) Q_PROPERTY(qreal currentWidth READ currentWidth NOTIFY currentWidthChanged)
public: public:
explicit DelegateSizeHelper(QObject *parent = nullptr); explicit DelegateSizeHelper(QObject *parent = nullptr);
QQuickItem *parentItem() const; qreal parentWidth() const;
void setParentItem(QQuickItem *parentItem); void setParentWidth(qreal parentWidth);
qreal leftPadding() const;
void setLeftPadding(qreal leftPadding);
qreal rightPadding() const;
void setRightPadding(qreal rightPadding);
qreal startBreakpoint() const; qreal startBreakpoint() const;
void setStartBreakpoint(qreal startBreakpoint); void setStartBreakpoint(qreal startBreakpoint);
qreal endBreakpoint() const; qreal endBreakpoint() const;
void setEndBreakpoint(qreal endBreakpoint); void setEndBreakpoint(qreal endBreakpoint);
int startPercentWidth() const; int startPercentWidth() const;
void setStartPercentWidth(int startPercentWidth); void setStartPercentWidth(int startPercentWidth);
int endPercentWidth() const; int endPercentWidth() const;
void setEndPercentWidth(int endPercentWidth); void setEndPercentWidth(int endPercentWidth);
qreal maxWidth() const; qreal maxWidth() const;
void setMaxWidth(qreal maxWidth); void setMaxWidth(qreal maxWidth);
qreal maxAvailableWidth() const; int currentPercentageWidth() const;
int availablePercentageWidth() const; qreal currentWidth() const;
qreal availableWidth() const;
/**
* @brief The left x position of the content column.
*/
qreal leftX() const;
/**
* @brief The right x position of the content column.
*/
qreal rightX() const;
Q_SIGNALS: Q_SIGNALS:
void parentItemChanged(); void parentWidthChanged();
void leftPaddingChanged();
void rightPaddingChanged();
void startBreakpointChanged(); void startBreakpointChanged();
void endBreakpointChanged(); void endBreakpointChanged();
void startPercentWidthChanged(); void startPercentWidthChanged();
void endPercentWidthChanged(); void endPercentWidthChanged();
void maxWidthChanged(); void maxWidthChanged();
void availablePercentageWidthChanged(); void currentPercentageWidthChanged();
void availableWidthChanged(); void currentWidthChanged();
private: private:
QPointer<QQuickItem> m_parentItem; qreal m_parentWidth = -1.0;
qreal m_startBreakpoint;
qreal m_endBreakpoint;
int m_startPercentWidth;
int m_endPercentWidth;
qreal m_maxWidth = -1.0;
qreal m_leftPadding = 0.0; int calculateCurrentPercentageWidth() const;
qreal m_rightPadding = 0.0;
qreal m_startBreakpoint = 0.0;
qreal m_endBreakpoint = 0.0;
int m_startPercentWidth = 100;
int m_endPercentWidth = 85;
std::optional<qreal> m_maxWidth = std::nullopt;
void calcWidthsChanged();
int calculateAvailablePercentageWidth() const;
}; };

View File

@@ -1,8 +1,8 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com> # SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
qt_add_library(Devtools STATIC) qt_add_library(devtools STATIC)
ecm_add_qml_module(Devtools GENERATE_PLUGIN_SOURCE ecm_add_qml_module(devtools GENERATE_PLUGIN_SOURCE
URI org.kde.neochat.devtools URI org.kde.neochat.devtools
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/devtools
QML_FILES QML_FILES
@@ -13,14 +13,4 @@ ecm_add_qml_module(Devtools GENERATE_PLUGIN_SOURCE
RoomData.qml RoomData.qml
ServerData.qml ServerData.qml
StateKeys.qml StateKeys.qml
SOURCES
models/statefiltermodel.cpp
models/statekeysmodel.cpp
models/statemodel.cpp
)
target_include_directories(Devtools PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(Devtools PRIVATE
Qt::Core
LibNeoChat
) )

View File

@@ -9,32 +9,34 @@ import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat import org.kde.neochat
FormCard.FormCard { FormCard.FormCardPage {
id: root id: root
Layout.topMargin: Kirigami.Units.largeSpacing FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
FormCard.FormCheckDelegate { FormCard.FormCheckDelegate {
text: i18nc("@option:check", "Show hidden events in the timeline") text: i18nc("@option:check", "Show hidden events in the timeline")
checked: NeoChatConfig.showAllEvents checked: NeoChatConfig.showAllEvents
onToggled: NeoChatConfig.showAllEvents = checked onToggled: NeoChatConfig.showAllEvents = checked
} }
FormCard.FormCheckDelegate { FormCard.FormCheckDelegate {
id: roomAccountDataVisibleCheck id: roomAccountDataVisibleCheck
text: i18nc("@option:check Enable the matrix 'threads' feature", "Always allow device verification") 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") description: i18n("Allow the user to start a verification session with devices that were already verified")
checked: NeoChatConfig.alwaysVerifyDevice checked: NeoChatConfig.alwaysVerifyDevice
onToggled: NeoChatConfig.alwaysVerifyDevice = checked onToggled: NeoChatConfig.alwaysVerifyDevice = checked
} }
FormCard.FormCheckDelegate { FormCard.FormCheckDelegate {
text: i18nc("@option:check", "Show focus in window header") text: i18nc("@option:check", "Show focus in window header")
checked: NeoChatConfig.windowTitleFocus checked: NeoChatConfig.windowTitleFocus
onToggled: { onToggled: {
NeoChatConfig.windowTitleFocus = checked; NeoChatConfig.windowTitleFocus = checked;
NeoChatConfig.save(); NeoChatConfig.save();
}
} }
} }
} }

View File

@@ -1,24 +1,21 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org> // SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later // SPDX-License-Identifier: GPL-2.0-or-later
pragma ComponentBehavior: Bound
import QtQuick import QtQuick
import QtQuick.Controls as QQC2 import QtQuick.Controls as QQC2
import QtQuick.Layouts import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat import org.kde.neochat
Kirigami.ScrollablePage { FormCard.FormCardPage {
id: root id: root
property NeoChatRoom room property NeoChatRoom room
required property NeoChatConnection connection required property NeoChatConnection connection
title: i18nc("@title", "Developer Tools") title: i18n("Developer Tools")
leftPadding: 0 leftPadding: 0
rightPadding: 0 rightPadding: 0
@@ -55,45 +52,22 @@ Kirigami.ScrollablePage {
} }
} }
Loader { StackLayout {
sourceComponent: switch (tabBar.currentIndex) { id: swipeView
case 0: return debugOptions;
case 1: return roomData; currentIndex: tabBar.currentIndex
case 2: return serverData;
case 3: return accountData;
case 4: return featureFlags;
}
}
Component {
id: debugOptions
DebugOptions {} DebugOptions {}
}
Component {
id: roomData
RoomData { RoomData {
room: root.room room: root.room
connection: root.connection connection: root.connection
} }
}
Component {
id: serverData
ServerData { ServerData {
connection: root.connection connection: root.connection
} }
}
Component {
id: accountData
AccountData { AccountData {
connection: root.connection connection: root.connection
} }
}
Component {
id: featureFlags
FeatureFlagPage {} FeatureFlagPage {}
} }
} }

Some files were not shown because too many files have changed in this diff Show More