Compare commits

..

1 Commits

Author SHA1 Message Date
James Graham
df53e15a57 Use master for Kirigami-Addons as unreleased patches are now required 2024-12-30 15:13:28 +00:00
170 changed files with 27650 additions and 42721 deletions

View File

@@ -2,7 +2,7 @@
; SPDX-License-Identifier: CC0-1.0 ; SPDX-License-Identifier: CC0-1.0
[BlueprintSettings] [BlueprintSettings]
kde/frameworks/extra-cmake-modules.version=master
kde/unreleased/kirigami-addons.version=master kde/unreleased/kirigami-addons.version=master
kde/applications/neochat.packageAppx=True kde/applications/neochat.packageAppx=True
libs/qt.qtMajorVersion=6 libs/qt.qtMajorVersion=6

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat", "id": "org.kde.neochat",
"branch": "master", "branch": "master",
"runtime": "org.kde.Platform", "runtime": "org.kde.Platform",
"runtime-version": "6.8", "runtime-version": "6.7",
"sdk": "org.kde.Sdk", "sdk": "org.kde.Sdk",
"command": "neochat", "command": "neochat",
"tags": [ "tags": [
@@ -25,23 +25,13 @@
"modules": [ "modules": [
{ {
"name": "kirigamiaddons", "name": "kirigamiaddons",
"config-opts": [ "config-opts": [ "-DBUILD_TESTING=OFF" ],
"-DBUILD_TESTING=OFF"
],
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"sources": [ "sources": [ { "type": "git", "url": "https://invent.kde.org/libraries/kirigami-addons.git", "commit": "34d311219e8b7209746a98b3a29b91ded05ff936" } ]
{
"type": "git",
"url": "https://invent.kde.org/libraries/kirigami-addons.git"
}
]
}, },
{ {
"name": "kquickimageeditor", "name": "kquickimageeditor",
"config-opts": [ "config-opts": [ "-DBUILD_WITH_QT6=ON" ],
"-DBUILD_WITH_QT6=ON",
"-DBUILD_TESTING=OFF"
],
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"sources": [ "sources": [
{ {
@@ -53,19 +43,17 @@
{ {
"name": "olm", "name": "olm",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"config-opts": [ "config-opts": [ "-DOLM_TESTS=OFF" ],
"-DOLM_TESTS=OFF"
],
"sources": [ "sources": [
{ {
"type": "git", "type": "git",
"url": "https://gitlab.matrix.org/matrix-org/olm.git", "url": "https://gitlab.matrix.org/matrix-org/olm.git",
"tag": "3.2.16", "tag": "3.2.10",
"x-checker-data": { "x-checker-data": {
"type": "git", "type": "git",
"tag-pattern": "^([\\d.]+)$" "tag-pattern": "^([\\d.]+)$"
}, },
"commit": "7e0c8277032e40308987257b711b38af8d77cc69" "commit": "9908862979147a71dc6abaecd521be526ae77be1"
} }
] ]
}, },
@@ -77,13 +65,13 @@
"-Dvapi=false", "-Dvapi=false",
"-Dgtk_doc=false", "-Dgtk_doc=false",
"-Dintrospection=false", "-Dintrospection=false",
"-Dcrypto=disabled" "-Dgcrypt=false"
], ],
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.6.tar.xz", "url": "https://download.gnome.org/sources/libsecret/0.20/libsecret-0.20.5.tar.xz",
"sha256": "747b8c175be108c880d3adfb9c3537ea66e520e4ad2dccf5dce58003aeeca090", "sha256": "3fb3ce340fcd7db54d87c893e69bfc2b1f6e4d4b279065ffe66dac9f0fd12b4d",
"x-checker-data": { "x-checker-data": {
"type": "gnome", "type": "gnome",
"name": "libsecret", "name": "libsecret",
@@ -98,13 +86,13 @@
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/0.15.0.tar.gz", "url": "https://github.com/frankosterfeld/qtkeychain/archive/0.14.2.tar.gz",
"sha256": "f4254dc8f0933b06d90672d683eab08ef770acd8336e44dfa030ce041dc2ca22", "sha256": "cf2e972b783ba66334a79a30f6b3a1ea794a1dc574d6c3bebae5ffd2f0399571",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 4138, "project-id": 4138,
"stable-only": true, "stable-only": true,
"url-template": "https://github.com/frankosterfeld/qtkeychain/archive/refs/tags/$version.tar.gz" "url-template": "https://github.com/frankosterfeld/qtkeychain/archive/v$version.tar.gz"
} }
} }
], ],
@@ -112,8 +100,7 @@
"-DBUILD_WITH_QT6=ON", "-DBUILD_WITH_QT6=ON",
"-DCMAKE_INSTALL_LIBDIR=/app/lib", "-DCMAKE_INSTALL_LIBDIR=/app/lib",
"-DLIB_INSTALL_DIR=/app/lib", "-DLIB_INSTALL_DIR=/app/lib",
"-DBUILD_TRANSLATIONS=NO", "-DBUILD_TRANSLATIONS=NO"
"-DBUILD_TESTING=OFF"
] ]
}, },
{ {
@@ -136,31 +123,29 @@
{ {
"name": "cmark", "name": "cmark",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"config-opts": [ "config-opts": [ "-DCMARK_TESTS=OFF" ],
"-DCMARK_TESTS=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=/app"
],
"sources": [ "sources": [
{ {
"type": "git", "type": "git",
"url": "https://github.com/commonmark/cmark.git" "url": "https://github.com/commonmark/cmark.git"
} }
], ],
"config-opts": [
"-DCMARK_TESTS=OFF",
"-DCMAKE_BUILD_TYPE=Release",
"-DCMAKE_INSTALL_PREFIX=/app"
],
"builddir": true "builddir": true
}, },
{ {
"name": "qcoro", "name": "qcoro",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"config-opts": [ "config-opts": [ "-DQCORO_BUILD_EXAMPLES=OFF", "-DBUILD_TESTING=OFF" ],
"-DQCORO_BUILD_EXAMPLES=OFF",
"-DBUILD_TESTING=OFF"
],
"sources": [ "sources": [
{ {
"type": "archive", "type": "archive",
"url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.11.0.tar.gz", "url": "https://github.com/danvratil/qcoro/archive/refs/tags/v0.7.0.tar.gz",
"sha256": "9942c5b4c533192f6c5954dc6d10178b3829075e6a621b67df73f0a4b74d8297", "sha256": "23ef0217926e67c8d2eb861cf91617da2f7d8d5a9ae6c62321b21448b1669210",
"x-checker-data": { "x-checker-data": {
"type": "anitya", "type": "anitya",
"project-id": 236236, "project-id": 236236,
@@ -173,15 +158,14 @@
{ {
"name": "neochat", "name": "neochat",
"buildsystem": "cmake-ninja", "buildsystem": "cmake-ninja",
"config-opts": [
"-DBUILD_TESTING=OFF",
"-DNEOCHAT_FLATPAK=ON"
],
"sources": [ "sources": [
{ {
"type": "dir", "type": "dir",
"path": "." "path": "."
} }
],
"config-opts": [
"-DNEOCHAT_FLATPAK=ON"
] ]
} }
] ]

View File

@@ -5,14 +5,10 @@ include:
- project: sysadmin/ci-utilities - project: sysadmin/ci-utilities
file: file:
- /gitlab-templates/reuse-lint.yml - /gitlab-templates/reuse-lint.yml
- /gitlab-templates/json-validation.yml
- /gitlab-templates/xml-lint.yml
- /gitlab-templates/yaml-lint.yml
- /gitlab-templates/android-qt6.yml - /gitlab-templates/android-qt6.yml
- /gitlab-templates/linux-qt6.yml - /gitlab-templates/linux-qt6.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

@@ -2,43 +2,42 @@
# SPDX-License-Identifier: BSD-2-Clause # SPDX-License-Identifier: BSD-2-Clause
Dependencies: Dependencies:
- 'on': ['Linux', 'Android', 'FreeBSD', 'Windows'] - 'on': ['Linux', 'Android', 'FreeBSD', 'Windows']
'require': 'require':
'frameworks/extra-cmake-modules': '@latest-kf6' 'frameworks/extra-cmake-modules': '@latest-kf6'
'frameworks/kcoreaddons': '@latest-kf6' 'frameworks/kcoreaddons': '@latest-kf6'
'frameworks/kirigami': '@latest-kf6' 'frameworks/kirigami': '@latest-kf6'
'frameworks/ki18n': '@latest-kf6' 'frameworks/ki18n': '@latest-kf6'
'frameworks/kconfig': '@latest-kf6' 'frameworks/kconfig': '@latest-kf6'
'frameworks/syntax-highlighting': '@latest-kf6' 'frameworks/syntax-highlighting': '@latest-kf6'
'frameworks/kitemmodels': '@latest-kf6' 'frameworks/kitemmodels': '@latest-kf6'
'frameworks/kquickcharts': '@latest-kf6' 'frameworks/kquickcharts': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6' 'frameworks/knotifications': '@latest-kf6'
'frameworks/kcolorscheme': '@latest-kf6' 'frameworks/kcolorscheme': '@latest-kf6'
'frameworks/kiconthemes': '@latest-kf6' 'libraries/kquickimageeditor': '@latest-kf6'
'libraries/kquickimageeditor': '@latest-kf6' 'frameworks/sonnet': '@latest-kf6'
'frameworks/sonnet': '@latest-kf6' 'frameworks/prison': '@latest-kf6'
'frameworks/prison': '@latest-kf6' 'libraries/kirigami-addons': '@latest-kf6'
'libraries/kirigami-addons': '@latest-kf6' 'third-party/libquotient': '@latest'
'third-party/libquotient': '@latest' 'third-party/qtkeychain': '@latest'
'third-party/qtkeychain': '@latest' 'third-party/cmark': '@latest'
'third-party/cmark': '@latest' 'third-party/qcoro': '@latest'
'third-party/qcoro': '@latest' - 'on': ['Windows', 'Linux', 'FreeBSD']
- 'on': ['Windows', 'Linux', 'FreeBSD'] 'require':
'require': 'frameworks/qqc2-desktop-style': '@latest-kf6'
'frameworks/qqc2-desktop-style': '@latest-kf6' 'frameworks/kio': '@latest-kf6'
'frameworks/kio': '@latest-kf6' 'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6' 'frameworks/kstatusnotifieritem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6' 'frameworks/kcrash': '@latest-kf6'
'frameworks/kcrash': '@latest-kf6' - 'on': ['Linux', 'FreeBSD']
- 'on': ['Linux', 'FreeBSD'] 'require':
'require': 'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/kdbusaddons': '@latest-kf6' 'frameworks/purpose': '@latest-kf6'
'frameworks/purpose': '@latest-kf6'
- 'on': ['Linux'] - 'on': ['Linux']
'require': 'require':
'sdk/selenium-webdriver-at-spi': '@latest-kf6' 'sdk/selenium-webdriver-at-spi': '@latest-kf6'
Options: Options:
per-test-timeout: 90 per-test-timeout: 90
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows'] require-passing-tests-on: [ 'Linux', 'Android', 'FreeBSD', 'Windows' ]

View File

@@ -8,8 +8,8 @@ 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 "04") set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MICRO "3") set(RELEASE_SERVICE_VERSION_MICRO "70")
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})
@@ -66,7 +66,7 @@ if (QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW) qt_policy(SET QTP0004 NEW)
endif () endif ()
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels IconThemes ColorScheme) find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
set_package_properties(KF6 PROPERTIES set_package_properties(KF6 PROPERTIES
TYPE REQUIRED TYPE REQUIRED
PURPOSE "Basic application components" PURPOSE "Basic application components"
@@ -174,9 +174,6 @@ 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)
add_subdirectory(memorytests)
endif()
endif() endif()
if(KF6DocTools_FOUND) if(KF6DocTools_FOUND)

View File

@@ -82,9 +82,3 @@ path = "src/purpose/purposeplugin.json"
precedence = "aggregate" precedence = "aggregate"
SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>" SPDX-FileCopyrightText = "2023 Tobias Fella <tobias.fella@kde.org>"
SPDX-License-Identifier = "BSD-2-Clause" SPDX-License-Identifier = "BSD-2-Clause"
[[annotations]]
path = "memorytests/memtest-sync.json"
precedence = "aggregate"
SPDX-FileCopyrightText = "2024 James Graham <james.h.graham@protonmail.com>"
SPDX-License-Identifier = "BSD-2-Clause"

View File

@@ -20,11 +20,11 @@ class ReactionModelTest : public QObject
private: private:
Connection *connection = nullptr; Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr; TestUtils::TestRoom *room = nullptr;
MessageContentModel *parentModel;
private Q_SLOTS: private Q_SLOTS:
void initTestCase(); void initTestCase();
void nullModel();
void basicReaction(); void basicReaction();
void newReaction(); void newReaction();
}; };
@@ -33,13 +33,20 @@ void ReactionModelTest::initTestCase()
{ {
connection = Connection::makeMockConnection(u"@bob:kde.org"_s); connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s); room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s);
parentModel = new MessageContentModel(room, "123456"_L1); }
void ReactionModelTest::nullModel()
{
auto model = ReactionModel(nullptr, nullptr);
QCOMPARE(model.rowCount(), 0);
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), QVariant());
} }
void ReactionModelTest::basicReaction() void ReactionModelTest::basicReaction()
{ {
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get()); auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto model = ReactionModel(parentModel, event->id(), room); auto model = ReactionModel(event, room);
QCOMPARE(model.rowCount(), 1); QCOMPARE(model.rowCount(), 1);
QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), u"<span style=\"font-family: 'emoji';\">👍</span>"_s); QCOMPARE(model.data(model.index(0), ReactionModel::TextContentRole), u"<span style=\"font-family: 'emoji';\">👍</span>"_s);
@@ -51,7 +58,7 @@ void ReactionModelTest::basicReaction()
void ReactionModelTest::newReaction() void ReactionModelTest::newReaction()
{ {
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get()); auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto model = new ReactionModel(parentModel, event->id(), room); auto model = new ReactionModel(event, room);
QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(), 1);
QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole), u"Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"_s); QCOMPARE(model->data(model->index(0), ReactionModel::ToolTipRole), u"Alice Margatroid reacted with <span style=\"font-family: 'emoji';\">👍</span>"_s);

View File

@@ -530,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

@@ -137,11 +137,7 @@ void TimelineMessageModelTest::pendingEvent()
model->setRoom(room); model->setRoom(room);
QCOMPARE(model->rowCount(), 0); QCOMPARE(model->rowCount(), 0);
#if Quotient_VERSION_MINOR > 9
auto txnId = room->postText("New plain message"_L1);
#else
auto txnId = room->postPlainText("New plain message"_L1); auto txnId = room->postPlainText("New plain message"_L1);
#endif
QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(), 1);
QCOMPARE(spyInsert.count(), 1); QCOMPARE(spyInsert.count(), 1);
@@ -149,11 +145,7 @@ void TimelineMessageModelTest::pendingEvent()
QCOMPARE(model->rowCount(), 0); QCOMPARE(model->rowCount(), 0);
QCOMPARE(spyRemove.count(), 1); QCOMPARE(spyRemove.count(), 1);
#if Quotient_VERSION_MINOR > 9
txnId = room->postText("New plain message"_L1);
#else
txnId = room->postPlainText("New plain message"_L1); txnId = room->postPlainText("New plain message"_L1);
#endif
QCOMPARE(model->rowCount(), 1); QCOMPARE(model->rowCount(), 1);
QCOMPARE(spyInsert.count(), 2); QCOMPARE(spyInsert.count(), 2);

View File

@@ -1,34 +0,0 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
qt_add_executable(timeline-memtest
main.cpp
)
target_link_libraries(timeline-memtest PRIVATE neochatplugin timelineplugin)
target_link_libraries(timeline-memtest PUBLIC
Qt::Core
Qt::Quick
Qt::Qml
Qt::Gui
Qt::QuickControls2
KF6::Kirigami
QuotientQt6
neochat
)
ecm_add_qml_module(timeline-memtest URI org.kde.neochat.timeline-memtest GENERATE_PLUGIN_SOURCE
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/timeline-memtest
QML_FILES
Main.qml
SOURCES
memtesttimelinemodel.cpp
memtesttimelinemodel.h
DEPENDENCIES
QtCore
QtQuick
IMPORTS
org.kde.neochat
)

View File

@@ -1,33 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
QQC2.ApplicationWindow {
id: root
title: "Timeline Memory Test"
minimumWidth: Kirigami.Units.gridUnit * 30
minimumHeight: Kirigami.Units.gridUnit * 30
visible: true
QQC2.ScrollView {
width: root.width
height: root.height
contentItem: ListView {
model: messageFilterModel
delegate: EventDelegate {
room: memTestTimelineModel.room
}
}
}
}

View File

@@ -1,30 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include <QApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include <KLocalizedString>
#include "memtesttimelinemodel.h"
#include "models/messagefiltermodel.h"
using namespace Qt::StringLiterals;
int main(int argc, char **argv)
{
QApplication app(argc, argv);
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
QQmlApplicationEngine engine;
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
MemTestTimelineModel *memTestTimelineModel = new MemTestTimelineModel;
MessageFilterModel *messageFilterModel = new MessageFilterModel(nullptr, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
return app.exec();
}

View File

@@ -1,655 +0,0 @@
{
"ephemeral": {
"events": [
{
"content": {
"$1000000000000:example.org": {
"m.read": {
"@alice:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000000:example.org": {
"m.read": {
"@bob:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@tim:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@example:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@jeff:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@tina:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@sally:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
},
{
"content": {
"$1000000000003:example.org": {
"m.read": {
"@fred:example.org": {
"ts": 1000000000000
}
}
}
},
"type": "m.receipt"
}
]
},
"state": {
"events": [
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Example",
"membership": "join"
},
"event_id": "$143273582555PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "example:example.org",
"state_key": "@example:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Alice",
"membership": "join",
"reason": "Looking for support"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "alice:example.org",
"state_key": "@alice:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Bob",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "bob:example.org",
"state_key": "@bob:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Tim",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "tim:example.org",
"state_key": "@tim:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Jeff",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "jeff:example.org",
"state_key": "@jeff:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Tina",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "tina:example.org",
"state_key": "@tina:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Sally",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "sally:example.org",
"state_key": "@sally:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
},
{
"content": {
"avatar_url": "mxc://example.org/SEsfnsuifSDFSSEF",
"displayname": "Fred",
"membership": "join"
},
"event_id": "$143273582443PhrSn:example.org",
"origin_server_ts": 1000000000000,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "fred:example.org",
"state_key": "@fred:example.org",
"type": "m.room.member",
"unsigned": {
"age": 1234
}
}
]
},
"timeline": {
"events": [
{
"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": "$1000000000000:example.org",
"origin_server_ts": 1000000000000,
"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": "$1000000000001:example.org",
"origin_server_ts": 1000000000001,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1233
}
},
{
"content": {
"m.relates_to": {
"event_id": "$1000000000001:example.org",
"key": "👍",
"rel_type": "m.annotation"
}
},
"origin_server_ts": 1000000000002,
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
"sender": "@alice:example.org",
"type": "m.reaction",
"unsigned": {
"age": 390159120
},
"event_id": "$1000000000002:example.org",
"age": 390159120
},
{
"content": {
"body": "reply",
"format": "org.matrix.custom.html",
"formatted_body": "reply",
"m.relates_to": {
"m.in_reply_to": {
"event_id": "$1000000000000:example.org"
}
},
"msgtype": "m.text"
},
"origin_server_ts": 1000000000003,
"sender": "@alice:example.org",
"type": "m.room.message",
"unsigned": {
"age": 98
},
"event_id": "$1000000000003: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": "$1000000000004:example.org",
"origin_server_ts": 1000000000004,
"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": "$1000000000005:example.org",
"origin_server_ts": 1000000000005,
"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": "$1000000000006:example.org",
"origin_server_ts": 1000000000006,
"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": "$1000000000007:example.org",
"origin_server_ts": 1000000000007,
"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": "$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,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -1,36 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "memtesttimelinemodel.h"
#include <Quotient/events/eventcontent.h>
#include <Quotient/events/roommessageevent.h>
using namespace Quotient;
MemTestTimelineModel::MemTestTimelineModel(QObject *parent)
: MessageModel(parent)
{
beginResetModel();
m_connection = Connection::makeMockConnection(u"@bob:example.org"_s);
m_room = new MemTestRoom(m_connection, u"#memtestroom:example.org"_s, u"memtest-sync.json"_s);
for (const auto &eventIt : m_room->messageEvents()) {
Q_EMIT newEventAdded(eventIt.event());
}
endResetModel();
}
std::optional<std::reference_wrapper<const RoomEvent>> MemTestTimelineModel::getEventForIndex(QModelIndex index) const
{
return *m_room->messageEvents().at(index.row()).event();
}
int MemTestTimelineModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_room->messageEvents().size();
}
#include "moc_memtesttimelinemodel.cpp"

View File

@@ -1,75 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/events/roomevent.h>
#include <Quotient/syncdata.h>
#include "models/messagemodel.h"
namespace Quotient
{
class Connection;
}
class NeoChatRoom;
class MemTestRoom : public NeoChatRoom
{
public:
MemTestRoom(Quotient::Connection *connection, const QString &roomName, const QString &syncFileName = {})
: NeoChatRoom(connection, roomName, Quotient::JoinState::Join)
{
syncNewEvents(syncFileName);
}
void update(Quotient::SyncRoomData &&data, bool fromCache = false)
{
Room::updateData(std::move(data), fromCache);
}
void syncNewEvents(const QString &syncFileName)
{
if (!syncFileName.isEmpty()) {
QFile testSyncFile;
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
testSyncFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson);
update(std::move(roomData));
}
}
};
/**
* @class MemTestTimelineModel
*
* This is a special version of the MessageModel design to load an unchanging set
* of events from a json file so that timeline memory optimisations can be measured.
*/
class MemTestTimelineModel : public MessageModel
{
Q_OBJECT
QML_ELEMENT
public:
explicit MemTestTimelineModel(QObject *parent = nullptr);
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
private:
QPointer<Quotient::Connection> m_connection;
std::vector<Quotient::RoomEventPtr> m_events;
std::optional<std::reference_wrapper<const Quotient::RoomEvent>> getEventForIndex(QModelIndex index) const override;
};

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,13 +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.3" date="2025-07-03"/>
<release version="25.04.2" date="2025-06-05"/>
<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.2" date="2025-02-06"/>
<release version="24.12.1" date="2025-01-09"/>
<release version="24.12.0" date="2024-12-12"/> <release version="24.12.0" date="2024-12-12"/>
<release version="24.08.3" date="2024-11-07"/> <release version="24.08.3" date="2024-11-07"/>
<release version="24.08.2" date="2024-10-10"/> <release version="24.08.2" date="2024-10-10"/>
@@ -659,9 +648,6 @@
<url>https://carlschwan.eu/2020/12/23/announcing-neochat-1.0-the-kde-matrix-client/</url> <url>https://carlschwan.eu/2020/12/23/announcing-neochat-1.0-the-kde-matrix-client/</url>
</release> </release>
</releases> </releases>
<requires>
<display_length compare="ge">360</display_length>
</requires>
<branding> <branding>
<color type="primary" scheme_preference="light">#a6e4f3</color> <color type="primary" scheme_preference="light">#a6e4f3</color>
<color type="primary" scheme_preference="dark">#235670</color> <color type="primary" scheme_preference="dark">#235670</color>

View File

@@ -109,12 +109,9 @@ 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[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
@@ -122,7 +119,6 @@ Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவு
Comment[tr]=Matrix üzerinde sohbet edin Comment[tr]=Matrix üzerinde sohbet edin
Comment[uk]=Спілкування у Matrix Comment[uk]=Спілкування у Matrix
Comment[x-test]=xxChat on Matrixxx Comment[x-test]=xxChat on Matrixxx
Comment[zh_CN]=在 Matrix 上聊天
Comment[zh_TW]=在 Matrix 上聊天 Comment[zh_TW]=在 Matrix 上聊天
MimeType=x-scheme-handler/matrix; MimeType=x-scheme-handler/matrix;
Exec=neochat %u Exec=neochat %u

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,9 +1,9 @@
# 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
--- ---
name: neochat name: neochat
base: core24 base: core22
adopt-info: neochat adopt-info: neochat
grade: stable grade: stable
confinement: strict confinement: strict
@@ -24,15 +24,12 @@ 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
package-repositories: package-repositories:
- type: apt - type: apt
ppa: ubuntu-toolchain-r/test ppa: ubuntu-toolchain-r/test
slots: slots:
session-dbus-interface: session-dbus-interface:
@@ -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
@@ -70,7 +66,7 @@ parts:
- -Dcrypto=disabled - -Dcrypto=disabled
- -Dgtk_doc=false - -Dgtk_doc=false
build-packages: build-packages:
- meson - meson
- libglib2.0-dev - libglib2.0-dev
- libgcrypt20-dev - libgcrypt20-dev
prime: prime:
@@ -85,7 +81,7 @@ 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
@@ -109,6 +105,8 @@ parts:
build-snaps: build-snaps:
- cmake - cmake
build-packages: build-packages:
- gcc-13
- g++-13
- libssl-dev - libssl-dev
cmake-parameters: cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
@@ -116,6 +114,9 @@ parts:
- -DBUILD_TESTING=OFF - -DBUILD_TESTING=OFF
- -DQuotient_ENABLE_E2EE=ON - -DQuotient_ENABLE_E2EE=ON
- -DBUILD_WITH_QT6=ON - -DBUILD_WITH_QT6=ON
override-build: |
update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-13 100 --slave /usr/bin/g++ g++ /usr/bin/g++-13 --slave /usr/bin/gcov gcov /usr/bin/gcov-13
craftctl default
prime: prime:
- -usr/include - -usr/include
- -usr/lib/*/pkgconfig - -usr/lib/*/pkgconfig
@@ -128,8 +129,6 @@ parts:
plugin: cmake plugin: cmake
build-environment: build-environment:
- PATH: /snap/bin:${PATH} - 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: cmake-parameters:
- -DCMAKE_INSTALL_PREFIX=/usr - -DCMAKE_INSTALL_PREFIX=/usr
- -DCMAKE_BUILD_TYPE=Release - -DCMAKE_BUILD_TYPE=Release
@@ -140,32 +139,17 @@ 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: .
plugin: cmake plugin: cmake
build-environment: build-environment:
- PATH: /snap/bin:${PATH} - 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"
build-packages: build-packages:
- cmark - cmark
- libcmark-dev - libcmark-dev
@@ -189,12 +173,3 @@ parts:
prime: prime:
- usr/lib/*/libcmark.so* - usr/lib/*/libcmark.so*
gpu-2404:
after: [neochat]
source: https://github.com/canonical/gpu-snap.git
plugin: dump
override-prime: |
craftctl default
${CRAFT_PART_SRC}/bin/gpu-2404-cleanup mesa-2404
prime:
- bin/gpu-2404-wrapper

View File

@@ -107,6 +107,8 @@ add_library(neochat STATIC
models/imagepacksmodel.h models/imagepacksmodel.h
events/imagepackevent.cpp events/imagepackevent.cpp
events/imagepackevent.h events/imagepackevent.h
events/joinrulesevent.cpp
events/joinrulesevent.h
models/reactionmodel.cpp models/reactionmodel.cpp
models/reactionmodel.h models/reactionmodel.h
delegatesizehelper.cpp delegatesizehelper.cpp
@@ -124,6 +126,8 @@ add_library(neochat STATIC
registration.cpp registration.cpp
neochatconnection.cpp neochatconnection.cpp
neochatconnection.h neochatconnection.h
jobs/neochatdeactivateaccountjob.cpp
jobs/neochatdeactivateaccountjob.h
jobs/neochatgetcommonroomsjob.cpp jobs/neochatgetcommonroomsjob.cpp
jobs/neochatgetcommonroomsjob.h jobs/neochatgetcommonroomsjob.h
mediasizehelper.cpp mediasizehelper.cpp
@@ -190,12 +194,6 @@ add_library(neochat STATIC
models/roomsortparametermodel.h models/roomsortparametermodel.h
models/messagemodel.cpp models/messagemodel.cpp
models/messagemodel.h 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 set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -206,9 +204,6 @@ if(ANDROID OR WIN32)
set_source_files_properties(qml/ShareActionStub.qml PROPERTIES set_source_files_properties(qml/ShareActionStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME ShareAction QT_QML_SOURCE_TYPENAME ShareAction
) )
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME GlobalMenu
)
endif() endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
@@ -218,7 +213,7 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/AccountMenu.qml qml/AccountMenu.qml
qml/ExploreComponent.qml qml/ExploreComponent.qml
qml/ExploreComponentMobile.qml qml/ExploreComponentMobile.qml
qml/RoomContextMenu.qml qml/ContextMenu.qml
qml/CollapsedRoomDelegate.qml qml/CollapsedRoomDelegate.qml
qml/RoomDelegate.qml qml/RoomDelegate.qml
qml/RoomListPage.qml qml/RoomListPage.qml
@@ -254,7 +249,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/MessageSourceSheet.qml qml/MessageSourceSheet.qml
qml/ConfirmEncryptionDialog.qml qml/ConfirmEncryptionDialog.qml
qml/RoomSearchPage.qml qml/RoomSearchPage.qml
qml/RoomPinnedMessagesPage.qml
qml/LocationChooser.qml qml/LocationChooser.qml
qml/TimelineView.qml qml/TimelineView.qml
qml/InvitationView.qml qml/InvitationView.qml
@@ -323,10 +317,7 @@ if(NOT ANDROID AND NOT WIN32)
qml/EditMenu.qml qml/EditMenu.qml
) )
else() else()
qt_target_qml_sources(neochat QML_FILES qt_target_qml_sources(neochat QML_FILES qml/ShareActionStub.qml)
qml/ShareActionStub.qml
qml/GlobalMenuStub.qml
)
endif() endif()
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h) configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
@@ -427,7 +418,6 @@ target_link_libraries(neochat PUBLIC
KF6::ConfigGui KF6::ConfigGui
KF6::CoreAddons KF6::CoreAddons
KF6::SonnetCore KF6::SonnetCore
KF6::IconThemes
KF6::ColorScheme KF6::ColorScheme
KF6::ItemModels KF6::ItemModels
QuotientQt6 QuotientQt6
@@ -501,7 +491,6 @@ if(ANDROID)
"network-connect" "network-connect"
"list-remove-user" "list-remove-user"
"org.kde.neochat" "org.kde.neochat"
"org.kde.neochat.tray"
"preferences-system-users" "preferences-system-users"
"preferences-desktop-theme-global" "preferences-desktop-theme-global"
"notifications" "notifications"
@@ -539,16 +528,12 @@ if(ANDROID)
"object-rotate-left" "object-rotate-left"
"object-rotate-right" "object-rotate-right"
"add-subtitle" "add-subtitle"
"security-high"
"security-low" "security-low"
"security-low-symbolic" "security-low-symbolic"
"kde" "kde"
"list-remove-symbolic" "list-remove-symbolic"
"edit-delete" "edit-delete"
"user-home-symbolic" "user-home-symbolic"
"pin-symbolic"
"kt-restore-defaults-symbolic"
"user-symbolic"
) )
ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android) ecm_add_android_apk(neochat-app ANDROID_DIR ${CMAKE_SOURCE_DIR}/android)
else() else()

View File

@@ -237,6 +237,20 @@ QQC2.Control {
onFormattingSelected: _private.formatText(format, selectionStart, selectionEnd) onFormattingSelected: _private.formatText(format, selectionStart, selectionEnd)
} }
Keys.onDeletePressed: {
if (selectedText.length > 0) {
remove(selectionStart, selectionEnd);
} else {
remove(cursorPosition, cursorPosition + 1);
}
if (textField.text == selectedText || textField.text.length <= 1) {
root.currentRoom.sendTypingNotification(false);
repeatTimer.stop();
}
if (quickFormatBar.visible) {
quickFormatBar.close();
}
}
Keys.onEnterPressed: event => { Keys.onEnterPressed: event => {
const controlIsPressed = event.modifiers & Qt.ControlModifier; const controlIsPressed = event.modifiers & Qt.ControlModifier;
if (completionMenu.visible) { if (completionMenu.visible) {
@@ -275,7 +289,7 @@ QQC2.Control {
completionMenu.decrementIndex(); completionMenu.decrementIndex();
} else if (event.key === Qt.Key_Down && completionMenu.visible) { } else if (event.key === Qt.Key_Down && completionMenu.visible) {
completionMenu.incrementIndex(); completionMenu.incrementIndex();
} else if (event.key === Qt.Key_Backspace || event.key === Qt.Key_Delete) { } else if (event.key === Qt.Key_Backspace) {
if (textField.text == selectedText || textField.text.length <= 1) { if (textField.text == selectedText || textField.text.length <= 1) {
root.currentRoom.sendTypingNotification(false); root.currentRoom.sendTypingNotification(false);
repeatTimer.stop(); repeatTimer.stop();
@@ -283,12 +297,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 = "";
@@ -315,13 +329,11 @@ QQC2.Control {
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source) icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
onClicked: modelData.trigger() onClicked: modelData.trigger()
padding: Kirigami.Units.smallSpacing
QQC2.ToolTip.visible: hovered QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: modelData.tooltip QQC2.ToolTip.text: modelData.tooltip
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
contentItem: PieProgressBar { PieProgressBar {
visible: modelData.isBusy visible: modelData.isBusy
progress: root.currentRoom.fileUploadingProgress progress: root.currentRoom.fileUploadingProgress
} }

View File

@@ -117,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]() {
@@ -226,14 +222,11 @@ void ChatDocumentHandler::complete(int index)
return; return;
} }
// Ensure we only search for the beginning of the current completion identifier
const auto fromIndex = qMax(completionStartIndex(), 0);
if (m_completionModel->autoCompletionType() == CompletionModel::User) { if (m_completionModel->autoCompletionType() == CompletionModel::User) {
auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::DisplayNameRole).toString(); auto name = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::DisplayNameRole).toString();
auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString(); auto id = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString();
auto text = getText(); auto text = getText();
auto at = text.indexOf(QLatin1Char('@'), fromIndex); auto at = text.lastIndexOf(QLatin1Char('@'), cursorPosition() - 1);
QTextCursor cursor(document()->textDocument()); QTextCursor cursor(document()->textDocument());
cursor.setPosition(at); cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
@@ -246,7 +239,7 @@ void ChatDocumentHandler::complete(int index)
} else if (m_completionModel->autoCompletionType() == CompletionModel::Command) { } else if (m_completionModel->autoCompletionType() == CompletionModel::Command) {
auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString(); auto command = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString();
auto text = getText(); auto text = getText();
auto at = text.indexOf(QLatin1Char('/'), fromIndex); auto at = text.lastIndexOf(QLatin1Char('/'));
QTextCursor cursor(document()->textDocument()); QTextCursor cursor(document()->textDocument());
cursor.setPosition(at); cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
@@ -254,7 +247,7 @@ void ChatDocumentHandler::complete(int index)
} else if (m_completionModel->autoCompletionType() == CompletionModel::Room) { } else if (m_completionModel->autoCompletionType() == CompletionModel::Room) {
auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString(); auto alias = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::SubtitleRole).toString();
auto text = getText(); auto text = getText();
auto at = text.indexOf(QLatin1Char('#'), fromIndex); auto at = text.lastIndexOf(QLatin1Char('#'), cursorPosition() - 1);
QTextCursor cursor(document()->textDocument()); QTextCursor cursor(document()->textDocument());
cursor.setPosition(at); cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
@@ -267,7 +260,7 @@ void ChatDocumentHandler::complete(int index)
} else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) { } else if (m_completionModel->autoCompletionType() == CompletionModel::Emoji) {
auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString(); auto shortcode = m_completionModel->data(m_completionModel->index(index, 0), CompletionModel::ReplacedTextRole).toString();
auto text = getText(); auto text = getText();
auto at = text.indexOf(QLatin1Char(':'), fromIndex); auto at = text.lastIndexOf(QLatin1Char(':'));
QTextCursor cursor(document()->textDocument()); QTextCursor cursor(document()->textDocument());
cursor.setPosition(at); cursor.setPosition(at);
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor); cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);

View File

@@ -168,6 +168,7 @@ void Controller::addConnection(NeoChatConnection *c)
connect(c, &NeoChatConnection::syncDone, this, [this, c]() { connect(c, &NeoChatConnection::syncDone, this, [this, c]() {
m_notificationsManager.handleNotifications(c); m_notificationsManager.handleNotifications(c);
}); });
connect(c, &NeoChatConnection::showInviteNotification, &m_notificationsManager, &NotificationsManager::postInviteNotification);
c->sync(); c->sync();
@@ -294,7 +295,7 @@ bool Controller::supportSystemTray() const
void Controller::setQuitOnLastWindowClosed() void Controller::setQuitOnLastWindowClosed()
{ {
#ifndef Q_OS_ANDROID #ifndef Q_OS_ANDROID
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) { if (NeoChatConfig::self()->systemTray()) {
m_trayIcon = new TrayIcon(this); m_trayIcon = new TrayIcon(this);
m_trayIcon->show(); m_trayIcon->show();
} else { } else {

View File

@@ -3,7 +3,6 @@
import QtQuick import QtQuick
import QtQuick.Layouts import QtQuick.Layouts
import QtQuick.Window
import org.kde.kirigami as Kirigami import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard import org.kde.kirigamiaddons.formcard as FormCard
@@ -38,7 +37,7 @@ FormCard.FormCardPage {
} }
function openEventSource(stateKey: string): void { function openEventSource(stateKey: string): void {
root.Window.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), { pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateKeysModel, model: stateKeysModel,
allowEdit: true, allowEdit: true,
room: root.room, room: root.room,

View File

@@ -50,17 +50,12 @@ public:
LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */ LiveLocation, /**< The initial event of a shared live location (i.e., the place where this is supposed to be shown in the timeline). */
Encrypted, /**< An encrypted message that cannot be decrypted. */ Encrypted, /**< An encrypted message that cannot be decrypted. */
Reply, /**< A component to show a replied-to message. */ Reply, /**< A component to show a replied-to message. */
Reaction, /**< A component to show the reactions to this message. */
LinkPreview, /**< A preview of a URL in the message. */ LinkPreview, /**< A preview of a URL in the message. */
LinkPreviewLoad, /**< A loading dialog for a link preview. */ LinkPreviewLoad, /**< A loading dialog for a link preview. */
ChatBar, /**< A text edit for editing a message. */ ChatBar, /**< A text edit for editing a message. */
ThreadRoot, /**< The root message of the thread. */
ThreadBody, /**< The other messages in the thread. */
ReplyButton, /**< A button to reply in the current thread. */ ReplyButton, /**< A button to reply in the current thread. */
FetchButton, /**< A button to fetch more messages in the current thread. */
Verification, /**< A user verification session start message. */ Verification, /**< A user verification session start message. */
Loading, /**< The component is loading. */ Loading, /**< The component is loading. */
Separator, /**< A horizontal separator. */
Other, /**< Anything that cannot be classified as another type. */ Other, /**< Anything that cannot be classified as another type. */
}; };
Q_ENUM(Type); Q_ENUM(Type);

View File

@@ -0,0 +1,16 @@
// SPDX-FileCopyrightText: 2019 Kitsune Ral <Kitsune-Ral@users.sf.net>
// SPDX-License-Identifier: LGPL-2.1-or-later
#include "joinrulesevent.h"
using namespace Quotient;
QString JoinRulesEvent::joinRule() const
{
return fromJson<QString>(contentJson()["join_rule"_L1]);
}
QJsonArray JoinRulesEvent::allow() const
{
return contentJson()["allow"_L1].toArray();
}

View File

@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2021 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: LGPL-2.1-or-later
#pragma once
#include <Quotient/events/stateevent.h>
namespace Quotient
{
/**
* @class JoinRulesEvent
*
* Class to define a join rule state event.
*
* @sa Quotient::StateEvent
*/
class JoinRulesEvent : public StateEvent
{
public:
QUO_EVENT(JoinRulesEvent, "m.room.join_rules")
explicit JoinRulesEvent(const QJsonObject &obj)
: StateEvent(obj)
{
}
/**
* @brief The join rule for the room.
*
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
* the available join rules for a room.
*/
QString joinRule() const;
/**
* @brief The allow rule for restricted rooms.
*
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
* full details on allow rules.
*/
QJsonArray allow() const;
};
}

View File

@@ -12,7 +12,6 @@ FileTransferPseudoJob::FileTransferPseudoJob(Operation operation, const QString
, m_eventId(eventId) , m_eventId(eventId)
, m_operation(operation) , m_operation(operation)
{ {
setCapabilities(KJob::Killable);
} }
void FileTransferPseudoJob::fileTransferProgress(const QString &id, qint64 progress, qint64 total) void FileTransferPseudoJob::fileTransferProgress(const QString &id, qint64 progress, qint64 total)
@@ -42,15 +41,6 @@ void FileTransferPseudoJob::fileTransferFailed(const QString &id, const QString
emitResult(); emitResult();
} }
void FileTransferPseudoJob::fileTransferCanceled(const QString &id)
{
if (id != m_eventId) {
return;
}
setError(KJob::KilledJobError);
emitResult();
}
void FileTransferPseudoJob::start() void FileTransferPseudoJob::start()
{ {
setTotalAmount(Unit::Files, 1); setTotalAmount(Unit::Files, 1);
@@ -59,9 +49,3 @@ void FileTransferPseudoJob::start()
{i18nc("The URL being downloaded/uploaded", "Source"), m_path}, {i18nc("The URL being downloaded/uploaded", "Source"), m_path},
{i18nc("The location being downloaded to", "Destination"), m_path}); {i18nc("The location being downloaded to", "Destination"), m_path});
} }
bool FileTransferPseudoJob::doKill()
{
Q_EMIT cancelRequested(m_eventId);
return true;
}

View File

@@ -15,7 +15,6 @@
*/ */
class FileTransferPseudoJob : public KJob class FileTransferPseudoJob : public KJob
{ {
Q_OBJECT
public: public:
enum Operation { enum Operation {
Download, Download,
@@ -39,22 +38,11 @@ public:
*/ */
void fileTransferFailed(const QString &id, const QString &errorMessage = {}); void fileTransferFailed(const QString &id, const QString &errorMessage = {});
/**
* @brief Set the file transfer as canceled.
*/
void fileTransferCanceled(const QString &id);
/** /**
* @brief Start the file transfer. * @brief Start the file transfer.
*/ */
void start() override; void start() override;
protected:
bool doKill() override;
Q_SIGNALS:
void cancelRequested(const QString &id);
private: private:
QString m_path; QString m_path;
QString m_eventId; QString m_eventId;

View File

@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "neochatdeactivateaccountjob.h"
using namespace Quotient;
NeoChatDeactivateAccountJob::NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth)
: BaseJob(HttpVerb::Post, u"DisableDeviceJob"_s, "_matrix/client/v3/account/deactivate")
{
QJsonObject data;
addParam<IfNotEmpty>(data, u"auth"_s, auth);
setRequestData(data);
}

View File

@@ -0,0 +1,12 @@
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Quotient/jobs/basejob.h>
class NeoChatDeactivateAccountJob : public Quotient::BaseJob
{
public:
explicit NeoChatDeactivateAccountJob(const std::optional<QJsonObject> &auth = {});
};

View File

@@ -9,6 +9,7 @@
#include <Quotient/events/roommessageevent.h> #include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "utils.h" #include "utils.h"
using namespace Quotient; using namespace Quotient;
@@ -21,6 +22,7 @@ LinkPreviewer::LinkPreviewer(const QUrl &url, QObject *parent)
Q_ASSERT(dynamic_cast<Connection *>(this->parent())); Q_ASSERT(dynamic_cast<Connection *>(this->parent()));
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged); connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
loadUrlPreview(); loadUrlPreview();
} }

View File

@@ -17,7 +17,6 @@ Kirigami.Page {
property bool showExisting: false property bool showExisting: false
property bool _showExisting: showExisting && root.currentStepString === root.initialStep property bool _showExisting: showExisting && root.currentStepString === root.initialStep
property bool showSettings: true
property alias currentStep: module.item property alias currentStep: module.item
property string currentStepString: initialStep property string currentStepString: initialStep
property string initialStep: "LoginRegister" property string initialStep: "LoginRegister"
@@ -266,7 +265,6 @@ Kirigami.Page {
FormCard.FormCard { FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing * 2 Layout.topMargin: Kirigami.Units.largeSpacing * 2
maximumWidth: Kirigami.Units.gridUnit * 20 maximumWidth: Kirigami.Units.gridUnit * 20
visible: root.showSettings
FormCard.FormButtonDelegate { FormCard.FormButtonDelegate {
text: i18nc("@action:button", "Settings") text: i18nc("@action:button", "Settings")
icon.name: "settings-configure" icon.name: "settings-configure"

View File

@@ -37,7 +37,6 @@
#include <KCrash> #include <KCrash>
#endif #endif
#include <KIconTheme>
#include <KLocalizedContext> #include <KLocalizedContext>
#include <KLocalizedString> #include <KLocalizedString>
@@ -102,7 +101,6 @@ Q_DECL_EXPORT
#endif #endif
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
KIconTheme::initTheme();
QNetworkProxyFactory::setUseSystemConfiguration(true); QNetworkProxyFactory::setUseSystemConfiguration(true);
#ifdef HAVE_WEBVIEW #ifdef HAVE_WEBVIEW
@@ -239,7 +237,6 @@ int main(int argc, char *argv[])
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin) Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
qml_register_types_org_kde_neochat(); qml_register_types_org_kde_neochat();
qmlRegisterUncreatableMetaObject(Quotient::staticMetaObject, "Quotient", 1, 0, "JoinRule", u"Access to JoinRule enum only"_s);
QQmlApplicationEngine engine; QQmlApplicationEngine engine;

View File

@@ -136,11 +136,7 @@ QList<ActionsModel::Action> actions{
Action{ Action{
u"plain"_s, u"plain"_s,
[](const QString &text, NeoChatRoom *room, ChatBarCache *) { [](const QString &text, NeoChatRoom *room, ChatBarCache *) {
#if Quotient_VERSION_MINOR > 9
room->postText(text.toHtmlEscaped());
#else
room->postPlainText(text.toHtmlEscaped()); room->postPlainText(text.toHtmlEscaped());
#endif
return QString(); return QString();
}, },
std::nullopt, std::nullopt,

View File

@@ -1,79 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "commonroomsmodel.h"
#include "jobs/neochatgetcommonroomsjob.h"
#include <QGuiApplication>
using namespace Quotient;
CommonRoomsModel::CommonRoomsModel(QObject *parent)
: QAbstractListModel(parent)
{
}
NeoChatConnection *CommonRoomsModel::connection() const
{
return m_connection;
}
void CommonRoomsModel::setConnection(NeoChatConnection *connection)
{
m_connection = connection;
Q_EMIT connectionChanged();
reload();
}
QString CommonRoomsModel::userId() const
{
return m_userId;
}
void CommonRoomsModel::setUserId(const QString &userId)
{
m_userId = userId;
Q_EMIT userIdChanged();
reload();
}
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
{
Q_UNUSED(index)
Q_UNUSED(roleName)
return {};
}
int CommonRoomsModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_commonRooms.size();
}
void CommonRoomsModel::reload()
{
if (!m_connection || m_userId.isEmpty()) {
return;
}
if (!m_connection->canCheckMutualRooms()) {
return;
}
// Checking if you have mutual rooms with yourself doesn't make sense and servers reject it too
if (m_connection->userId() == m_userId) {
return;
}
m_connection->callApi<NeochatGetCommonRoomsJob>(m_userId).then([this](const auto job) {
const auto &replyData = job->jsonData();
beginResetModel();
for (const auto &roomId : replyData[u"joined"_s].toArray()) {
m_commonRooms.push_back(roomId.toString());
}
endResetModel();
Q_EMIT countChanged();
});
}
#include "moc_commonroomsmodel.cpp"

View File

@@ -1,58 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include "neochatconnection.h"
#include "neochatroom.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/roommember.h>
/**
* @brief Model to show the common or mutual rooms between you and another user.
*/
class CommonRoomsModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(NeoChatConnection *connection WRITE setConnection READ connection NOTIFY connectionChanged REQUIRED)
Q_PROPERTY(QString userId WRITE setUserId READ userId NOTIFY userIdChanged REQUIRED)
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
public:
enum Roles {
TextRole = Qt::DisplayRole,
LongitudeRole,
LatitudeRole,
AssetRole,
AuthorRole,
};
Q_ENUM(Roles)
explicit CommonRoomsModel(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] QString userId() const;
void setUserId(const QString &userId);
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
Q_SIGNALS:
void connectionChanged();
void userIdChanged();
void countChanged();
private:
void reload();
QPointer<NeoChatConnection> m_connection;
QString m_userId;
QList<QString> m_commonRooms;
};

View File

@@ -39,6 +39,14 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(TimelineMessageModel::TimeRole).toDateTime().toLocalTime().date(); const auto previousEventDay = mapToSource(this->index(index.row() + 1, 0)).data(TimelineMessageModel::TimeRole).toDateTime().toLocalTime().date();
return day != previousEventDay; return day != previousEventDay;
} }
// Catch and force the author to be shown for all rows
if (role == TimelineMessageModel::ContentModelRole) {
const auto model = qvariant_cast<MessageContentModel *>(mapToSource(index).data(TimelineMessageModel::ContentModelRole));
if (model != nullptr) {
model->setShowAuthor(true);
}
return QVariant::fromValue<MessageContentModel *>(model);
}
QVariantMap mediaInfo = mapToSource(index).data(TimelineMessageModel::MediaInfoRole).toMap(); QVariantMap mediaInfo = mapToSource(index).data(TimelineMessageModel::MediaInfoRole).toMap();

View File

@@ -1,37 +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
#include "messagecontentfiltermodel.h"
#include "enums/messagecomponenttype.h"
#include "models/messagecontentmodel.h"
MessageContentFilterModel::MessageContentFilterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
}
bool MessageContentFilterModel::showAuthor() const
{
return m_showAuthor;
}
void MessageContentFilterModel::setShowAuthor(bool showAuthor)
{
if (showAuthor == m_showAuthor) {
return;
}
m_showAuthor = showAuthor;
Q_EMIT showAuthorChanged();
}
bool MessageContentFilterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
if (m_showAuthor) {
return true;
}
const auto index = sourceModel()->index(source_row, 0, source_parent);
auto contentType = static_cast<MessageComponentType::Type>(index.data(MessageContentModel::ComponentTypeRole).toInt());
return contentType != MessageComponentType::Author;
}

View File

@@ -1,43 +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
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
/**
* @class MessageContentFilterModel
*
* This model filters a message's contents.
*/
class MessageContentFilterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief Whether the author component should be shown.
*/
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
public:
explicit MessageContentFilterModel(QObject *parent = nullptr);
bool showAuthor() const;
void setShowAuthor(bool showAuthor);
Q_SIGNALS:
void showAuthorChanged();
protected:
/**
* @brief Whether a row should be shown out or not.
*
* @sa QSortFilterProxyModel::filterAcceptsRow
*/
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private:
bool m_showAuthor = true;
};

View File

@@ -3,7 +3,6 @@
#include "messagecontentmodel.h" #include "messagecontentmodel.h"
#include "eventhandler.h" #include "eventhandler.h"
#include "messagecomponenttype.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include <QImageReader> #include <QImageReader>
@@ -13,7 +12,7 @@
#include <Quotient/events/roommessageevent.h> #include <Quotient/events/roommessageevent.h>
#include <Quotient/events/stickerevent.h> #include <Quotient/events/stickerevent.h>
#include <Quotient/qt_connection_util.h> #include <Quotient/qt_connection_util.h>
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1) #if Quotient_VERSION_MINOR > 9
#include <Quotient/thread.h> #include <Quotient/thread.h>
#endif #endif
@@ -28,7 +27,6 @@
#include "chatbarcache.h" #include "chatbarcache.h"
#include "filetype.h" #include "filetype.h"
#include "linkpreviewer.h" #include "linkpreviewer.h"
#include "models/reactionmodel.h"
#include "neochatconnection.h" #include "neochatconnection.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "texthandler.h" #include "texthandler.h"
@@ -132,16 +130,19 @@ void MessageContentModel::initializeModel()
connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() { connect(m_room, &NeoChatRoom::urlPreviewEnabledChanged, this, [this]() {
resetContent(); resetContent();
}); });
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, [this]() {
resetContent();
});
connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) { connect(m_room, &Room::memberNameUpdated, this, [this](RoomMember member) {
if (m_room != nullptr) { if (m_room != nullptr) {
if (senderId().isEmpty() || senderId() == member.id()) { if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) {
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole}); Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
} }
} }
}); });
connect(m_room, &Room::memberAvatarUpdated, this, [this](RoomMember member) { connect(m_room, &Room::memberAvatarUpdated, this, [this](RoomMember member) {
if (m_room != nullptr) { if (m_room != nullptr) {
if (senderId().isEmpty() || senderId() == member.id()) { if (m_eventSenderId.isEmpty() || m_eventSenderId == member.id()) {
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole}); Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
} }
} }
@@ -151,18 +152,12 @@ void MessageContentModel::initializeModel()
updateReplyModel(); updateReplyModel();
resetModel(); resetModel();
}); });
connect(m_room, &Room::updatedEvent, this, [this](const QString &eventId) {
if (eventId == m_eventId) {
updateReactionModel();
}
});
initializeEvent(); initializeEvent();
if (m_currentState == Available || m_currentState == Pending) { if (m_currentState == Available || m_currentState == Pending) {
updateReplyModel(); updateReplyModel();
} }
resetModel(); resetModel();
updateReactionModel();
} }
void MessageContentModel::initializeEvent() void MessageContentModel::initializeEvent()
@@ -183,6 +178,16 @@ void MessageContentModel::initializeEvent()
} else { } else {
m_currentState = Available; m_currentState = Available;
} }
if (m_eventSenderObject == nullptr) {
auto senderId = eventResult.first->senderId();
// A pending event might not have a sender ID set yet but in that case it must
// be the local member.
if (senderId.isEmpty()) {
senderId = m_room->localMember().id();
}
m_eventSenderObject = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_room, senderId));
}
Q_EMIT eventUpdated(); Q_EMIT eventUpdated();
} }
@@ -213,29 +218,30 @@ void MessageContentModel::getEvent()
m_room->downloadEventFromServer(m_eventId); m_room->downloadEventFromServer(m_eventId);
} }
QString MessageContentModel::senderId() const bool MessageContentModel::showAuthor() const
{ {
const auto eventResult = m_room->getEvent(m_eventId); return m_showAuthor;
if (eventResult.first == nullptr) {
return {};
}
auto senderId = eventResult.first->senderId();
if (senderId.isEmpty()) {
senderId = m_room->localMember().id();
}
return senderId;
} }
NeochatRoomMember *MessageContentModel::senderObject() const void MessageContentModel::setShowAuthor(bool showAuthor)
{ {
const auto eventResult = m_room->getEvent(m_eventId); if (showAuthor == m_showAuthor) {
if (eventResult.first == nullptr) { return;
return nullptr;
} }
if (eventResult.first->senderId().isEmpty()) { m_showAuthor = showAuthor;
return m_room->qmlSafeMember(m_room->localMember().id());
if (m_room->connection()->isIgnored(m_eventSenderId)) {
if (showAuthor) {
beginInsertRows({}, 0, 0);
m_components.prepend(MessageComponent{MessageComponentType::Author, QString(), {}});
endInsertRows();
} else {
beginRemoveRows({}, 0, 0);
m_components.remove(0, 1);
endRemoveRows();
}
} }
return m_room->qmlSafeMember(eventResult.first->senderId()); Q_EMIT showAuthorChanged();
} }
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer; static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
@@ -269,7 +275,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
} }
if (role == DisplayRole) { if (role == DisplayRole) {
if (m_currentState == UnAvailable || m_room->connection()->isIgnored(senderId())) { if (m_currentState == UnAvailable || m_room->connection()->isIgnored(m_eventSenderId)) {
Kirigami::Platform::PlatformTheme *theme = Kirigami::Platform::PlatformTheme *theme =
static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true)); static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
@@ -311,7 +317,7 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
return EventHandler::timeString(m_room, event.first, u"hh:mm"_s, m_currentState == Pending); return EventHandler::timeString(m_room, event.first, u"hh:mm"_s, m_currentState == Pending);
} }
if (role == AuthorRole) { if (role == AuthorRole) {
return QVariant::fromValue<NeochatRoomMember *>(senderObject()); return QVariant::fromValue<NeochatRoomMember *>(m_eventSenderObject.get());
} }
if (role == MediaInfoRole) { if (role == MediaInfoRole) {
return EventHandler::mediaInfo(m_room, event.first); return EventHandler::mediaInfo(m_room, event.first);
@@ -345,21 +351,6 @@ QVariant MessageContentModel::data(const QModelIndex &index, int role) const
if (role == ReplyContentModelRole) { if (role == ReplyContentModelRole) {
return QVariant::fromValue<MessageContentModel *>(m_replyModel); return QVariant::fromValue<MessageContentModel *>(m_replyModel);
} }
if (role == ReactionModelRole) {
return QVariant::fromValue<ReactionModel *>(m_reactionModel);
;
}
if (role == ThreadRootRole) {
auto roomMessageEvent = eventCast<const RoomMessageEvent>(event.first);
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
if (roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))) {
#else
if (roomMessageEvent && roomMessageEvent->isThreaded()) {
#endif
return roomMessageEvent->threadRootEventId();
}
return {};
}
if (role == LinkPreviewerRole) { if (role == LinkPreviewerRole) {
if (component.type == MessageComponentType::LinkPreview) { if (component.type == MessageComponentType::LinkPreview) {
return QVariant::fromValue<LinkPreviewer *>( return QVariant::fromValue<LinkPreviewer *>(
@@ -386,33 +377,27 @@ int MessageContentModel::rowCount(const QModelIndex &parent) const
QHash<int, QByteArray> MessageContentModel::roleNames() const QHash<int, QByteArray> MessageContentModel::roleNames() const
{ {
return roleNamesStatic(); QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
} roles[DisplayRole] = "display";
roles[ComponentTypeRole] = "componentType";
QHash<int, QByteArray> MessageContentModel::roleNamesStatic() roles[ComponentAttributesRole] = "componentAttributes";
{ roles[EventIdRole] = "eventId";
QHash<int, QByteArray> roles; roles[TimeRole] = "time";
roles[MessageContentModel::DisplayRole] = "display"; roles[TimeStringRole] = "timeString";
roles[MessageContentModel::ComponentTypeRole] = "componentType"; roles[AuthorRole] = "author";
roles[MessageContentModel::ComponentAttributesRole] = "componentAttributes"; roles[MediaInfoRole] = "mediaInfo";
roles[MessageContentModel::EventIdRole] = "eventId"; roles[FileTransferInfoRole] = "fileTransferInfo";
roles[MessageContentModel::TimeRole] = "time"; roles[ItineraryModelRole] = "itineraryModel";
roles[MessageContentModel::TimeStringRole] = "timeString"; roles[LatitudeRole] = "latitude";
roles[MessageContentModel::AuthorRole] = "author"; roles[LongitudeRole] = "longitude";
roles[MessageContentModel::MediaInfoRole] = "mediaInfo"; roles[AssetRole] = "asset";
roles[MessageContentModel::FileTransferInfoRole] = "fileTransferInfo"; roles[PollHandlerRole] = "pollHandler";
roles[MessageContentModel::ItineraryModelRole] = "itineraryModel"; roles[ReplyEventIdRole] = "replyEventId";
roles[MessageContentModel::LatitudeRole] = "latitude"; roles[ReplyAuthorRole] = "replyAuthor";
roles[MessageContentModel::LongitudeRole] = "longitude"; roles[ReplyContentModelRole] = "replyContentModel";
roles[MessageContentModel::AssetRole] = "asset"; roles[ThreadRootRole] = "threadRoot";
roles[MessageContentModel::PollHandlerRole] = "pollHandler"; roles[LinkPreviewerRole] = "linkPreviewer";
roles[MessageContentModel::ReplyEventIdRole] = "replyEventId"; roles[ChatBarCacheRole] = "chatBarCache";
roles[MessageContentModel::ReplyAuthorRole] = "replyAuthor";
roles[MessageContentModel::ReplyContentModelRole] = "replyContentModel";
roles[MessageContentModel::ReactionModelRole] = "reactionModel";
roles[MessageContentModel::ThreadRootRole] = "threadRoot";
roles[MessageContentModel::LinkPreviewerRole] = "linkPreviewer";
roles[MessageContentModel::ChatBarCacheRole] = "chatBarCache";
return roles; return roles;
} }
@@ -421,7 +406,7 @@ void MessageContentModel::resetModel()
beginResetModel(); beginResetModel();
m_components.clear(); m_components.clear();
if (m_room->connection()->isIgnored(senderId()) || m_currentState == UnAvailable) { if (m_room->connection()->isIgnored(m_eventSenderId) || m_currentState == UnAvailable) {
m_components += MessageComponent{MessageComponentType::Text, QString(), {}}; m_components += MessageComponent{MessageComponentType::Text, QString(), {}};
endResetModel(); endResetModel();
return; return;
@@ -434,7 +419,9 @@ void MessageContentModel::resetModel()
return; return;
} }
m_components += MessageComponent{MessageComponentType::Author, QString(), {}}; if (m_showAuthor) {
m_components += MessageComponent{MessageComponentType::Author, QString(), {}};
}
m_components += messageContentComponents(); m_components += messageContentComponents();
endResetModel(); endResetModel();
@@ -490,23 +477,8 @@ QList<MessageComponent> MessageContentModel::messageContentComponents(bool isEdi
newComponents = addLinkPreviews(newComponents); newComponents = addLinkPreviews(newComponents);
} }
if ((m_reactionModel && m_reactionModel->rowCount() > 0)) {
newComponents += MessageComponent{MessageComponentType::Reaction, QString(), {}};
}
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1)
if (NeoChatConfig::self()->threads() && roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))
&& roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
#else
if (NeoChatConfig::self()->threads() && roomMessageEvent && roomMessageEvent->isThreaded()
&& roomMessageEvent->id() == roomMessageEvent->threadRootEventId()) {
#endif
newComponents += MessageComponent{MessageComponentType::Separator, {}, {}};
newComponents += MessageComponent{MessageComponentType::ThreadBody, u"Thread Body"_s, {}};
}
// If the event is already threaded the ThreadModel will handle displaying a chat bar. // If the event is already threaded the ThreadModel will handle displaying a chat bar.
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1) #if Quotient_VERSION_MINOR > 9
if (isThreading && roomMessageEvent && !(roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))) { if (isThreading && roomMessageEvent && !(roomMessageEvent->isThreaded() || m_room->threads().contains(roomMessageEvent->id()))) {
#else #else
if (isThreading && roomMessageEvent && roomMessageEvent->isThreaded()) { if (isThreading && roomMessageEvent && roomMessageEvent->isThreaded()) {
@@ -528,7 +500,7 @@ void MessageContentModel::updateReplyModel()
if (roomMessageEvent == nullptr) { if (roomMessageEvent == nullptr) {
return; return;
} }
if (!roomMessageEvent->isReply(!NeoChatConfig::self()->threads()) || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) { if (!roomMessageEvent->isReply() || (roomMessageEvent->isThreaded() && NeoChatConfig::self()->threads())) {
if (m_replyModel) { if (m_replyModel) {
delete m_replyModel; delete m_replyModel;
} }
@@ -539,7 +511,7 @@ void MessageContentModel::updateReplyModel()
return; return;
} }
m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(!NeoChatConfig::self()->threads()), true, false, this); m_replyModel = new MessageContentModel(m_room, roomMessageEvent->replyEventId(), true, false, this);
connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() { connect(m_replyModel, &MessageContentModel::eventUpdated, this, [this]() {
Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole}); Q_EMIT dataChanged(index(0), index(0), {ReplyAuthorRole});
@@ -746,25 +718,4 @@ void MessageContentModel::updateItineraryModel()
} }
} }
void MessageContentModel::updateReactionModel()
{
if (m_reactionModel != nullptr && m_reactionModel->rowCount() > 0) {
return;
}
if (m_reactionModel == nullptr) {
m_reactionModel = new ReactionModel(this, m_eventId, m_room);
connect(m_reactionModel, &ReactionModel::reactionsUpdated, this, &MessageContentModel::updateReactionModel);
}
if (m_reactionModel->rowCount() <= 0) {
m_reactionModel->disconnect(this);
delete m_reactionModel;
m_reactionModel = nullptr;
return;
}
resetContent();
}
#include "moc_messagecontentmodel.cpp" #include "moc_messagecontentmodel.cpp"

View File

@@ -7,11 +7,11 @@
#include <QQmlEngine> #include <QQmlEngine>
#include <Quotient/events/roomevent.h> #include <Quotient/events/roomevent.h>
#include <Quotient/room.h>
#include "enums/messagecomponenttype.h" #include "enums/messagecomponenttype.h"
#include "itinerarymodel.h" #include "itinerarymodel.h"
#include "messagecomponent.h" #include "messagecomponent.h"
#include "models/reactionmodel.h"
#include "neochatroommember.h" #include "neochatroommember.h"
/** /**
@@ -25,6 +25,11 @@ class MessageContentModel : public QAbstractListModel
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("") QML_UNCREATABLE("")
/**
* @brief Whether the author component is being shown.
*/
Q_PROPERTY(bool showAuthor READ showAuthor WRITE setShowAuthor NOTIFY showAuthorChanged)
public: public:
enum MessageState { enum MessageState {
Unknown, /**< The message state is unknown. */ Unknown, /**< The message state is unknown. */
@@ -57,8 +62,6 @@ public:
ReplyAuthorRole, /**< The author of the event that was replied to. */ ReplyAuthorRole, /**< The author of the event that was replied to. */
ReplyContentModelRole, /**< The MessageContentModel for the reply event. */ ReplyContentModelRole, /**< The MessageContentModel for the reply event. */
ReactionModelRole, /**< Reaction model for this event. */
ThreadRootRole, /**< The thread root event ID for the event. */ ThreadRootRole, /**< The thread root event ID for the event. */
LinkPreviewerRole, /**< The link preview details. */ LinkPreviewerRole, /**< The link preview details. */
@@ -72,6 +75,9 @@ public:
bool isPending = false, bool isPending = false,
MessageContentModel *parent = nullptr); MessageContentModel *parent = nullptr);
bool showAuthor() const;
void setShowAuthor(bool showAuthor);
/** /**
* @brief Get the given role value at the given index. * @brief Get the given role value at the given index.
* *
@@ -93,8 +99,6 @@ public:
*/ */
[[nodiscard]] QHash<int, QByteArray> roleNames() const override; [[nodiscard]] QHash<int, QByteArray> roleNames() const override;
static QHash<int, QByteArray> roleNamesStatic();
/** /**
* @brief Close the link preview at the given index. * @brief Close the link preview at the given index.
* *
@@ -109,10 +113,11 @@ Q_SIGNALS:
private: private:
QPointer<NeoChatRoom> m_room; QPointer<NeoChatRoom> m_room;
QString m_eventId; QString m_eventId;
QString senderId() const; QString m_eventSenderId;
NeochatRoomMember *senderObject() const; std::unique_ptr<NeochatRoomMember> m_eventSenderObject = nullptr;
MessageState m_currentState = Unknown; MessageState m_currentState = Unknown;
bool m_showAuthor = true;
bool m_isReply; bool m_isReply;
void initializeModel(); void initializeModel();
@@ -127,7 +132,6 @@ private:
QPointer<MessageContentModel> m_replyModel; QPointer<MessageContentModel> m_replyModel;
void updateReplyModel(); void updateReplyModel();
ReactionModel *m_reactionModel = nullptr;
ItineraryModel *m_itineraryModel = nullptr; ItineraryModel *m_itineraryModel = nullptr;
QList<MessageComponent> componentsForType(MessageComponentType::Type type); QList<MessageComponent> componentsForType(MessageComponentType::Type type);
@@ -138,6 +142,4 @@ private:
void updateItineraryModel(); void updateItineraryModel();
bool m_emptyItinerary = false; bool m_emptyItinerary = false;
void updateReactionModel();
}; };

View File

@@ -9,11 +9,12 @@
#include "enums/delegatetype.h" #include "enums/delegatetype.h"
#include "messagecontentmodel.h" #include "messagecontentmodel.h"
#include "neochatconfig.h" #include "neochatconfig.h"
#include "neochatroommember.h"
#include "timelinemessagemodel.h" #include "timelinemessagemodel.h"
using namespace Quotient; using namespace Quotient;
MessageFilterModel::MessageFilterModel(QObject *parent, QAbstractItemModel *sourceModel) MessageFilterModel::MessageFilterModel(QObject *parent, TimelineModel *sourceModel)
: QSortFilterProxyModel(parent) : QSortFilterProxyModel(parent)
{ {
Q_ASSERT(sourceModel); Q_ASSERT(sourceModel);
@@ -92,8 +93,12 @@ QVariant MessageFilterModel::data(const QModelIndex &index, int role) const
return authorList(mapToSource(index).row()); return authorList(mapToSource(index).row());
} else if (role == ExcessAuthorsRole) { } else if (role == ExcessAuthorsRole) {
return excessAuthors(mapToSource(index).row()); return excessAuthors(mapToSource(index).row());
} else if (role == MessageModel::ShowAuthorRole) { } else if (role == TimelineMessageModel::ContentModelRole) {
return showAuthor(index); const auto model = qvariant_cast<MessageContentModel *>(mapToSource(index).data(TimelineMessageModel::ContentModelRole));
if (model != nullptr && !showAuthor(index)) {
model->setShowAuthor(false);
}
return QVariant::fromValue<MessageContentModel *>(model);
} }
return QSortFilterProxyModel::data(index, role); return QSortFilterProxyModel::data(index, role);
} }

View File

@@ -37,7 +37,7 @@ public:
LastRole, // Keep this last LastRole, // Keep this last
}; };
explicit MessageFilterModel(QObject *parent = nullptr, QAbstractItemModel *sourceModel = nullptr); explicit MessageFilterModel(QObject *parent = nullptr, TimelineModel *sourceModel = nullptr);
/** /**
* @brief Custom filter function to remove hidden messages. * @brief Custom filter function to remove hidden messages.

View File

@@ -5,10 +5,9 @@
#include "neochatconfig.h" #include "neochatconfig.h"
#include <Quotient/events/encryptedevent.h>
#include <Quotient/events/roommessageevent.h> #include <Quotient/events/roommessageevent.h>
#include <Quotient/events/stickerevent.h> #include <Quotient/events/stickerevent.h>
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1) #if Quotient_VERSION_MINOR > 9
#include <Quotient/thread.h> #include <Quotient/thread.h>
#endif #endif
@@ -19,7 +18,6 @@
#include "eventhandler.h" #include "eventhandler.h"
#include "events/pollevent.h" #include "events/pollevent.h"
#include "models/reactionmodel.h" #include "models/reactionmodel.h"
#include "neochatroommember.h"
using namespace Quotient; using namespace Quotient;
@@ -56,9 +54,6 @@ void MessageModel::setRoom(NeoChatRoom *room)
beginResetModel(); beginResetModel();
m_room = room; m_room = room;
if (m_room != nullptr) {
m_room->setVisible(true);
}
Q_EMIT roomChanged(); Q_EMIT roomChanged();
endResetModel(); endResetModel();
} }
@@ -121,15 +116,16 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
} }
if (role == ContentModelRole) { if (role == ContentModelRole) {
if (event->get().is<EncryptedEvent>() || event->get().is<StickerEvent>()) { QString modelId;
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(event->get().id())); if (!event->get().id().isEmpty() && m_contentModels.contains(event->get().id())) {
modelId = event.value().get().id();
} else if (!event.value().get().transactionId().isEmpty() && m_contentModels.contains(event.value().get().transactionId())) {
modelId = event.value().get().transactionId();
} }
if (!modelId.isEmpty()) {
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get()); return QVariant::fromValue<MessageContentModel *>(m_contentModels.at(modelId).get());
if (NeoChatConfig::self()->threads() && roomMessageEvent && roomMessageEvent->isThreaded()) {
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(roomMessageEvent->threadRootEventId()));
} }
return QVariant::fromValue<MessageContentModel *>(m_room->contentModelForEvent(&event->get())); return {};
} }
if (role == GenericDisplayRole) { if (role == GenericDisplayRole) {
@@ -148,11 +144,11 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
mId = event.value().get().senderId(); mId = event.value().get().senderId();
} }
if (!m_room->isMember(mId)) { if (!m_memberObjects.contains(mId)) {
return QVariant::fromValue<NeochatRoomMember *>(emptyNeochatRoomMember); return QVariant::fromValue<NeochatRoomMember *>(emptyNeochatRoomMember);
} }
return QVariant::fromValue<NeochatRoomMember *>(m_room->qmlSafeMember(mId)); return QVariant::fromValue<NeochatRoomMember *>(m_memberObjects.at(mId).get());
} }
if (role == HighlightRole) { if (role == HighlightRole) {
@@ -178,7 +174,7 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
} }
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get()); auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get());
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 1) #if Quotient_VERSION_MINOR > 9
if (roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(event.value().get().id()))) { if (roomMessageEvent && (roomMessageEvent->isThreaded() || m_room->threads().contains(event.value().get().id()))) {
const auto &thread = m_room->threads().value(roomMessageEvent->isThreaded() ? roomMessageEvent->threadRootEventId() : event.value().get().id()); const auto &thread = m_room->threads().value(roomMessageEvent->isThreaded() ? roomMessageEvent->threadRootEventId() : event.value().get().id());
if (thread.latestEventId != event.value().get().id()) { if (thread.latestEventId != event.value().get().id()) {
@@ -201,15 +197,13 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
if (role == ProgressInfoRole) { if (role == ProgressInfoRole) {
if (auto e = eventCast<const RoomMessageEvent>(&event.value().get())) { if (auto e = eventCast<const RoomMessageEvent>(&event.value().get())) {
if (e->has<EventContent::FileContent>() || e->has<EventContent::ImageContent>() || e->has<EventContent::VideoContent>() if (e->has<EventContent::FileContent>()) {
|| e->has<EventContent::AudioContent>()) {
return QVariant::fromValue(m_room->cachedFileTransferInfo(&event.value().get())); return QVariant::fromValue(m_room->cachedFileTransferInfo(&event.value().get()));
} }
} }
if (eventCast<const StickerEvent>(&event.value().get())) { if (eventCast<const StickerEvent>(&event.value().get())) {
return QVariant::fromValue(m_room->cachedFileTransferInfo(&event.value().get())); return QVariant::fromValue(m_room->cachedFileTransferInfo(&event.value().get()));
} }
return {};
} }
if (role == TimeRole) { if (role == TimeRole) {
@@ -221,9 +215,6 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
} }
if (role == IsThreadedRole) { if (role == IsThreadedRole) {
if (!NeoChatConfig::self()->threads()) {
return false;
}
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get())) { if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event.value().get())) {
return roomMessageEvent->isThreaded(); return roomMessageEvent->isThreaded();
} }
@@ -266,6 +257,18 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
return m_readMarkerModels.contains(event.value().get().id()); return m_readMarkerModels.contains(event.value().get().id());
} }
if (role == ReactionRole) {
if (m_reactionModels.contains(event.value().get().id())) {
return QVariant::fromValue<ReactionModel *>(m_reactionModels[event.value().get().id()].data());
} else {
return QVariantList();
}
}
if (role == ShowReactionsRole) {
return m_reactionModels.contains(event.value().get().id());
}
if (role == VerifiedRole) { if (role == VerifiedRole) {
if (event.value().get().originalEvent()) { if (event.value().get().originalEvent()) {
auto encrypted = dynamic_cast<const EncryptedEvent *>(event.value().get().originalEvent()); auto encrypted = dynamic_cast<const EncryptedEvent *>(event.value().get().originalEvent());
@@ -296,10 +299,6 @@ QVariant MessageModel::data(const QModelIndex &idx, int role) const
&& event.value().get().senderId() == m_room->localMember().id(); && event.value().get().senderId() == m_room->localMember().id();
} }
if (role == ShowAuthorRole) {
return true;
}
return {}; return {};
} }
@@ -319,6 +318,8 @@ QHash<int, QByteArray> MessageModel::roleNames() const
roles[ShowSectionRole] = "showSection"; roles[ShowSectionRole] = "showSection";
roles[ReadMarkersRole] = "readMarkers"; roles[ReadMarkersRole] = "readMarkers";
roles[ShowReadMarkersRole] = "showReadMarkers"; roles[ShowReadMarkersRole] = "showReadMarkers";
roles[ReactionRole] = "reaction";
roles[ShowReactionsRole] = "showReactions";
roles[VerifiedRole] = "verified"; roles[VerifiedRole] = "verified";
roles[AuthorDisplayNameRole] = "authorDisplayName"; roles[AuthorDisplayNameRole] = "authorDisplayName";
roles[IsRedactedRole] = "isRedacted"; roles[IsRedactedRole] = "isRedacted";
@@ -327,7 +328,6 @@ QHash<int, QByteArray> MessageModel::roleNames() const
roles[ContentModelRole] = "contentModel"; roles[ContentModelRole] = "contentModel";
roles[MediaInfoRole] = "mediaInfo"; roles[MediaInfoRole] = "mediaInfo";
roles[IsEditableRole] = "isEditable"; roles[IsEditableRole] = "isEditable";
roles[ShowAuthorRole] = "showAuthor";
return roles; return roles;
} }
@@ -424,6 +424,21 @@ void MessageModel::createEventObjects(const Quotient::RoomEvent *event, bool isP
senderId = m_room->localMember().id(); senderId = m_room->localMember().id();
} }
if (!m_memberObjects.contains(senderId)) {
m_memberObjects[senderId] = std::unique_ptr<NeochatRoomMember>(new NeochatRoomMember(m_room, senderId));
}
if (!m_contentModels.contains(eventId) && !m_contentModels.contains(event->transactionId())) {
if (!event->isStateEvent() || event->matrixType() == u"org.matrix.msc3672.beacon_info"_s) {
m_contentModels[eventId] = std::unique_ptr<MessageContentModel>(new MessageContentModel(m_room, eventId, false, isPending));
}
}
const auto roomMessageEvent = eventCast<const Quotient::RoomMessageEvent>(event);
if (roomMessageEvent && roomMessageEvent->isThreaded() && !m_threadModels.contains(roomMessageEvent->threadRootEventId())) {
m_threadModels[roomMessageEvent->threadRootEventId()] = QSharedPointer<ThreadModel>(new ThreadModel(roomMessageEvent->threadRootEventId(), m_room));
}
// ReadMarkerModel handles updates to add and remove markers, we only need to // ReadMarkerModel handles updates to add and remove markers, we only need to
// handle adding and removing whole models here. // handle adding and removing whole models here.
if (m_readMarkerModels.contains(eventId)) { if (m_readMarkerModels.contains(eventId)) {
@@ -448,22 +463,42 @@ void MessageModel::createEventObjects(const Quotient::RoomEvent *event, bool isP
} }
} }
} }
if (const auto roomEvent = eventCast<const RoomMessageEvent>(event)) {
// ReactionModel handles updates to add and remove reactions, we only need to
// handle adding and removing whole models here.
if (m_reactionModels.contains(eventId)) {
// If a model already exists but now has no reactions remove it
if (m_reactionModels[eventId]->rowCount() <= 0) {
m_reactionModels.remove(eventId);
if (!resetting) {
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
}
}
} else {
if (m_room->relatedEvents(*event, Quotient::EventRelation::AnnotationType).count() > 0) {
// If a model doesn't exist and there are reactions add it.
auto reactionModel = QSharedPointer<ReactionModel>(new ReactionModel(roomEvent, m_room));
if (reactionModel->rowCount() > 0) {
m_reactionModels[eventId] = reactionModel;
if (!resetting) {
refreshEventRoles(eventId, {ReactionRole, ShowReactionsRole});
}
}
}
}
}
} }
void MessageModel::clearModel() void MessageModel::clearModel()
{ {
if (m_room) { if (m_room) {
const auto oldRoom = m_room;
// HACK: Reset the model to a null room first to make sure QML dismantles // HACK: Reset the model to a null room first to make sure QML dismantles
// last room's objects before the room is actually changed // last room's objects before the room is actually changed
beginResetModel(); beginResetModel();
m_room->disconnect(this); m_room->disconnect(this);
m_room = nullptr; m_room = nullptr;
endResetModel(); endResetModel();
// Because we don't want any of the object deleted before the model is cleared.
oldRoom->setVisible(false);
} }
// Don't clear the member objects until the model has been fully reset and all // Don't clear the member objects until the model has been fully reset and all
@@ -473,6 +508,9 @@ void MessageModel::clearModel()
void MessageModel::clearEventObjects() void MessageModel::clearEventObjects()
{ {
m_memberObjects.clear();
m_contentModels.clear();
m_reactionModels.clear();
m_readMarkerModels.clear(); m_readMarkerModels.clear();
} }
@@ -486,7 +524,7 @@ bool MessageModel::event(QEvent *event)
ThreadModel *MessageModel::threadModelForRootId(const QString &threadRootId) const ThreadModel *MessageModel::threadModelForRootId(const QString &threadRootId) const
{ {
return m_room->modelForThread(threadRootId); return m_threadModels[threadRootId].data();
} }
#include "moc_messagemodel.cpp" #include "moc_messagemodel.cpp"

View File

@@ -7,7 +7,9 @@
#include <QQmlEngine> #include <QQmlEngine>
#include <functional> #include <functional>
#include "messagecontentmodel.h"
#include "neochatroom.h" #include "neochatroom.h"
#include "neochatroommember.h"
#include "pollhandler.h" #include "pollhandler.h"
#include "readmarkermodel.h" #include "readmarkermodel.h"
#include "threadmodel.h" #include "threadmodel.h"
@@ -17,27 +19,11 @@ class ReactionModel;
/** /**
* @class MessageModel * @class MessageModel
* *
* This class defines a base model for visualising the room events. * This class defines a model for visualising the room events.
* *
* On its own MessageModel will result in an empty timeline as there is no mechanism * This model covers all event types in the room with many of the roles being
* to retrieve events. This is by design as it allows the model to be inherited from * specific to a subset of events. This means the user needs to understand which
* and the user can specify their own source of events, e.g. a room timeline or a * roles will return useful information for a given event type.
* search result.
*
* The inherited model MUST do the following:
* - Define methods for retrieving events
* - Call newEventAdded() for each new event in the model so that all the required
* event objects are created.
* - Override getEventForIndex() so that the data() function can get an event for a
* given index
* - Override rowCount()
*
* Optionally the new model can:
* - override timelineServerIndex() if dealing with pending events otherwise the default
* function returns 0 which is correct for other use cases.
* - m_lastReadEventIndex is available to track a read marker location (so that the
* data function can output the appropriate values). The new class must implement
* the functionality to add, move, remove, etc though.
* *
* @sa NeoChatRoom * @sa NeoChatRoom
*/ */
@@ -45,7 +31,6 @@ class MessageModel : public QAbstractListModel
{ {
Q_OBJECT Q_OBJECT
QML_ELEMENT QML_ELEMENT
QML_UNCREATABLE("")
/** /**
* @brief The current room that the model is getting its messages from. * @brief The current room that the model is getting its messages from.
@@ -77,13 +62,14 @@ public:
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */ ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */ ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
ReactionRole, /**< List model for this event. */
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */ VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */ AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
IsRedactedRole, /**< Whether an event has been deleted. */ IsRedactedRole, /**< Whether an event has been deleted. */
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */ IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
IsEditableRole, /**< Whether the event can be edited by the user. */ IsEditableRole, /**< Whether the event can be edited by the user. */
ShowAuthorRole, /**< Whether the author of a message should be shown. */
LastRole, // Keep this last LastRole, // Keep this last
}; };
Q_ENUM(EventRoles) Q_ENUM(EventRoles)
@@ -112,23 +98,10 @@ public:
*/ */
Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const; Q_INVOKABLE [[nodiscard]] int eventIdToRow(const QString &eventID) const;
/**
* @brief Get a ThreadModel for the give thread root Matrix ID.
*/
Q_INVOKABLE ThreadModel *threadModelForRootId(const QString &threadRootId) const; Q_INVOKABLE ThreadModel *threadModelForRootId(const QString &threadRootId) const;
Q_SIGNALS: Q_SIGNALS:
/**
* @brief Emitted when the room is changed.
*/
void roomChanged(); void roomChanged();
/**
* @brief A signal to tell the MessageModel that a new event has been added.
*
* Any model inheriting from MessageModel needs to emit this signal for every
* new event it adds.
*/
void newEventAdded(const Quotient::RoomEvent *event, bool isPending = false); void newEventAdded(const Quotient::RoomEvent *event, bool isPending = false);
protected: protected:
@@ -152,7 +125,11 @@ private:
bool resetting = false; bool resetting = false;
bool movingEvent = false; bool movingEvent = false;
std::map<QString, std::unique_ptr<NeochatRoomMember>> m_memberObjects;
std::map<QString, std::unique_ptr<MessageContentModel>> m_contentModels;
QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels; QMap<QString, QSharedPointer<ReadMarkerModel>> m_readMarkerModels;
QMap<QString, QSharedPointer<ThreadModel>> m_threadModels;
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false); void createEventObjects(const Quotient::RoomEvent *event, bool isPending = false);
}; };

View File

@@ -121,10 +121,6 @@ void NotificationsModel::loadData()
const auto &authorAvatar = avatar.isValid() && avatar.scheme() == u"mxc"_s ? avatar : QUrl(); const auto &authorAvatar = avatar.isValid() && avatar.scheme() == u"mxc"_s ? avatar : QUrl();
const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get()); const auto &roomEvent = eventCast<const RoomEvent>(notification.event.get());
if (!roomEvent) {
continue;
}
beginInsertRows({}, m_notifications.length(), m_notifications.length()); beginInsertRows({}, m_notifications.length(), m_notifications.length());
m_notifications += Notification{ m_notifications += Notification{
.roomId = notification.roomId, .roomId = notification.roomId,

View File

@@ -1,67 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "pinnedmessagemodel.h"
#include "enums/delegatetype.h"
#include "eventhandler.h"
#include "models/messagecontentmodel.h"
#include "neochatroom.h"
#include <QGuiApplication>
#include <KLocalizedString>
using namespace Quotient;
PinnedMessageModel::PinnedMessageModel(QObject *parent)
: MessageModel(parent)
{
connect(this, &MessageModel::roomChanged, this, &PinnedMessageModel::fill);
}
bool PinnedMessageModel::loading() const
{
return m_loading;
}
int PinnedMessageModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return m_pinnedEvents.size();
}
std::optional<std::reference_wrapper<const Quotient::RoomEvent>> PinnedMessageModel::getEventForIndex(const QModelIndex index) const
{
if (static_cast<size_t>(index.row()) >= m_pinnedEvents.size() || index.row() < 0) {
return std::nullopt;
}
return std::reference_wrapper{*m_pinnedEvents[index.row()].get()};
}
void PinnedMessageModel::setLoading(bool loading)
{
m_loading = loading;
Q_EMIT loadingChanged();
}
void PinnedMessageModel::fill()
{
if (!m_room) {
return;
}
const auto events = m_room->pinnedEventIds();
for (const auto &event : std::as_const(events)) {
auto job = m_room->connection()->callApi<GetOneRoomEventJob>(m_room->id(), event);
connect(job, &BaseJob::success, this, [this, job] {
beginInsertRows({}, m_pinnedEvents.size(), m_pinnedEvents.size());
m_pinnedEvents.push_back(std::move(fromJson<event_ptr_tt<RoomEvent>>(job->jsonData())));
Q_EMIT newEventAdded(m_pinnedEvents.back().get(), false);
endInsertRows();
});
}
}
#include "moc_pinnedmessagemodel.cpp"

View File

@@ -1,62 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QAbstractListModel>
#include <QQmlEngine>
#include <QString>
#include <Quotient/csapi/rooms.h>
#include "messagemodel.h"
#include "neochatroommember.h"
namespace Quotient
{
class Connection;
}
class NeoChatRoom;
/**
* @class PinnedMessageModel
*
* This class defines the model for visualising a room's pinned messages.
*/
class PinnedMessageModel : public MessageModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief Whether the model is currently loading.
*/
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
public:
explicit PinnedMessageModel(QObject *parent = nullptr);
/**
* @brief Number of rows in the model.
*
* @sa QAbstractItemModel::rowCount
*/
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
bool loading() const;
Q_SIGNALS:
void loadingChanged();
protected:
std::optional<std::reference_wrapper<const Quotient::RoomEvent>> getEventForIndex(QModelIndex index) const override;
private:
void setLoading(bool loading);
void fill();
bool m_loading = false;
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_pinnedEvents;
};

View File

@@ -9,27 +9,22 @@
#include <KLocalizedString> #include <KLocalizedString>
#include "neochatroom.h"
using namespace Qt::StringLiterals; using namespace Qt::StringLiterals;
ReactionModel::ReactionModel(MessageContentModel *parent, const QString &eventId, NeoChatRoom *room) ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room)
: QAbstractListModel(parent) : QAbstractListModel(nullptr)
, m_room(room) , m_room(room)
, m_eventId(eventId) , m_event(event)
{ {
Q_ASSERT(parent); if (m_event != nullptr && m_room != nullptr) {
Q_ASSERT(parent != nullptr); connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
Q_ASSERT(!eventId.isEmpty()); if (m_event && m_event->id() == eventId) {
Q_ASSERT(room != nullptr); updateReactions();
}
});
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) { updateReactions();
if (m_eventId == eventId) { }
updateReactions();
}
});
updateReactions();
} }
QVariant ReactionModel::data(const QModelIndex &index, int role) const QVariant ReactionModel::data(const QModelIndex &index, int role) const
@@ -104,16 +99,12 @@ int ReactionModel::rowCount(const QModelIndex &parent) const
void ReactionModel::updateReactions() void ReactionModel::updateReactions()
{ {
if (m_room == nullptr) {
return;
}
beginResetModel(); beginResetModel();
m_reactions.clear(); m_reactions.clear();
m_shortcodes.clear(); m_shortcodes.clear();
const auto &annotations = m_room->relatedEvents(m_eventId, Quotient::EventRelation::AnnotationType); const auto &annotations = m_room->relatedEvents(*m_event, Quotient::EventRelation::AnnotationType);
if (annotations.isEmpty()) { if (annotations.isEmpty()) {
endResetModel(); endResetModel();
return; return;

View File

@@ -3,20 +3,11 @@
#pragma once #pragma once
#include "neochatroom.h"
#include <QAbstractListModel> #include <QAbstractListModel>
#include <QQmlEngine>
#include <Quotient/events/reactionevent.h> #include <Quotient/events/reactionevent.h>
#include <Quotient/roommember.h> #include <Quotient/roommember.h>
namespace Quotient
{
class RoomMessageEvent;
}
class MessageContentModel;
class NeoChatRoom;
/** /**
* @class ReactionModel * @class ReactionModel
* *
@@ -47,7 +38,7 @@ public:
HasLocalMember, /**< Whether the local member is in the list of authors. */ HasLocalMember, /**< Whether the local member is in the list of authors. */
}; };
explicit ReactionModel(MessageContentModel *parent, const QString &eventId, NeoChatRoom *room); explicit ReactionModel(const Quotient::RoomMessageEvent *event, NeoChatRoom *room);
/** /**
* @brief Get the given role value at the given index. * @brief Get the given role value at the given index.
@@ -70,15 +61,9 @@ public:
*/ */
[[nodiscard]] QHash<int, QByteArray> roleNames() const override; [[nodiscard]] QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
/**
* @brief The reactions in the model have been updated.
*/
void reactionsUpdated();
private: private:
QPointer<NeoChatRoom> m_room; QPointer<NeoChatRoom> m_room;
QString m_eventId; const Quotient::RoomMessageEvent *m_event;
QList<Reaction> m_reactions; QList<Reaction> m_reactions;
QMap<QString, QString> m_shortcodes; QMap<QString, QString> m_shortcodes;

View File

@@ -348,12 +348,6 @@ QVariant RoomTreeModel::data(const QModelIndex &index, int role) const
return QVariant::fromValue(room); return QVariant::fromValue(room);
} }
if (role == SubtitleTextRole) { if (role == SubtitleTextRole) {
if (room->isInvite()) {
if (room->isDirectChat()) {
return i18nc("@info:label", "Invited you to chat");
}
return i18nc("@info:label", "%1 invited you", room->member(room->invitingUserId()).displayName());
}
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) { if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
return QString(); return QString();
} }

View File

@@ -11,18 +11,18 @@
#include "chatbarcache.h" #include "chatbarcache.h"
#include "eventhandler.h" #include "eventhandler.h"
#include "messagecomponenttype.h" #include "messagecomponenttype.h"
#include "messagecontentmodel.h"
#include "neochatroom.h" #include "neochatroom.h"
ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room) ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
: QConcatenateTablesProxyModel(room) : QConcatenateTablesProxyModel(room)
, m_threadRootId(threadRootId) , m_threadRootId(threadRootId)
, m_threadFetchModel(new ThreadFetchModel(this))
, m_threadChatBarModel(new ThreadChatBarModel(this, room)) , m_threadChatBarModel(new ThreadChatBarModel(this, room))
{ {
Q_ASSERT(!m_threadRootId.isEmpty()); Q_ASSERT(!m_threadRootId.isEmpty());
Q_ASSERT(room); Q_ASSERT(room);
m_threadRootContentModel = std::unique_ptr<MessageContentModel>(new MessageContentModel(room, threadRootId));
#if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0) #if Quotient_VERSION_MINOR > 9 || (Quotient_VERSION_MINOR == 9 && Quotient_VERSION_PATCH > 0)
connect(room, &Quotient::Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) { connect(room, &Quotient::Room::pendingEventAdded, this, [this](const Quotient::RoomEvent *event) {
#else #else
@@ -49,7 +49,7 @@ ThreadModel::ThreadModel(const QString &threadRootId, NeoChatRoom *room)
// If the thread was created by the local user fetchMore() won't find the current // If the thread was created by the local user fetchMore() won't find the current
// pending event. // pending event.
checkPending(); checkPending();
fetchMoreEvents(3); fetchMore({});
addModels(); addModels();
} }
@@ -73,28 +73,34 @@ QString ThreadModel::threadRootId() const
return m_threadRootId; return m_threadRootId;
} }
QHash<int, QByteArray> ThreadModel::roleNames() const MessageContentModel *ThreadModel::threadRootContentModel() const
{ {
return MessageContentModel::roleNamesStatic(); return m_threadRootContentModel.get();
} }
bool ThreadModel::moreEventsAvailable(const QModelIndex &parent) const QHash<int, QByteArray> ThreadModel::roleNames() const
{
return m_threadRootContentModel->roleNames();
}
bool ThreadModel::canFetchMore(const QModelIndex &parent) const
{ {
Q_UNUSED(parent); Q_UNUSED(parent);
return !m_currentJob && m_nextBatch.has_value(); return !m_currentJob && m_nextBatch.has_value();
} }
void ThreadModel::fetchMoreEvents(int max) void ThreadModel::fetchMore(const QModelIndex &parent)
{ {
Q_UNUSED(parent);
if (!m_currentJob && m_nextBatch.has_value()) { if (!m_currentJob && m_nextBatch.has_value()) {
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent()); const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
const auto connection = room->connection(); const auto connection = room->connection();
m_currentJob = connection->callApi<Quotient::GetRelatingEventsWithRelTypeJob>(room->id(), m_threadRootId, u"m.thread"_s, *m_nextBatch, QString(), max); m_currentJob = connection->callApi<Quotient::GetRelatingEventsWithRelTypeJob>(room->id(), m_threadRootId, u"m.thread"_s, *m_nextBatch, QString(), 5);
Q_EMIT moreEventsAvailableChanged();
connect(m_currentJob, &Quotient::BaseJob::success, this, [this]() { connect(m_currentJob, &Quotient::BaseJob::success, this, [this]() {
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
auto newEvents = m_currentJob->chunk(); auto newEvents = m_currentJob->chunk();
for (auto &event : newEvents) { for (auto &event : newEvents) {
m_events.push_back(event->id()); m_contentModels.push_back(new MessageContentModel(room, event->id()));
} }
addModels(); addModels();
@@ -110,18 +116,18 @@ void ThreadModel::fetchMoreEvents(int max)
} }
m_currentJob.clear(); m_currentJob.clear();
Q_EMIT moreEventsAvailableChanged();
}); });
} }
} }
void ThreadModel::addNewEvent(const Quotient::RoomEvent *event) void ThreadModel::addNewEvent(const Quotient::RoomEvent *event)
{ {
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent());
auto eventId = event->id(); auto eventId = event->id();
if (eventId.isEmpty()) { if (eventId.isEmpty()) {
eventId = event->transactionId(); eventId = event->transactionId();
} }
m_events.push_front(eventId); m_contentModels.push_front(new MessageContentModel(room, eventId));
} }
void ThreadModel::addModels() void ThreadModel::addModels()
@@ -130,16 +136,9 @@ void ThreadModel::addModels()
clearModels(); clearModels();
} }
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent()); addSourceModel(m_threadRootContentModel.get());
if (room == nullptr) { for (auto it = m_contentModels.crbegin(); it != m_contentModels.crend(); ++it) {
return; addSourceModel(*it);
}
addSourceModel(m_threadFetchModel);
for (auto it = m_events.crbegin(); it != m_events.crend(); ++it) {
const auto contentModel = room->contentModelForEvent(*it);
if (contentModel != nullptr) {
addSourceModel(room->contentModelForEvent(*it));
}
} }
addSourceModel(m_threadChatBarModel); addSourceModel(m_threadChatBarModel);
@@ -149,90 +148,15 @@ void ThreadModel::addModels()
void ThreadModel::clearModels() void ThreadModel::clearModels()
{ {
const auto room = dynamic_cast<NeoChatRoom *>(QObject::parent()); removeSourceModel(m_threadRootContentModel.get());
if (room == nullptr) { for (const auto &model : m_contentModels) {
return; if (sourceModels().contains(model)) {
} removeSourceModel(model);
removeSourceModel(m_threadFetchModel);
for (const auto &model : m_events) {
const auto contentModel = room->contentModelForEvent(model);
if (sourceModels().contains(contentModel)) {
removeSourceModel(contentModel);
} }
} }
removeSourceModel(m_threadChatBarModel); removeSourceModel(m_threadChatBarModel);
} }
void ThreadModel::closeLinkPreview(int row)
{
if (row < 0 || row >= rowCount()) {
return;
}
const auto index = this->index(row, 0);
if (!index.isValid()) {
return;
}
const auto sourceIndex = mapToSource(index);
const auto sourceModel = sourceIndex.model();
if (sourceModel == nullptr) {
return;
}
// This is a bit silly but we can only get a const reference to the model from the
// index so we need to search the source models.
for (const auto &model : sourceModels()) {
if (model == sourceModel) {
const auto sourceContentModel = dynamic_cast<MessageContentModel *>(model);
if (sourceContentModel == nullptr) {
return;
}
sourceContentModel->closeLinkPreview(sourceIndex.row());
}
}
}
ThreadFetchModel::ThreadFetchModel(QObject *parent)
: QAbstractListModel(parent)
{
const auto threadModel = dynamic_cast<ThreadModel *>(parent);
Q_ASSERT(threadModel != nullptr);
connect(threadModel, &ThreadModel::moreEventsAvailableChanged, this, [this]() {
beginResetModel();
endResetModel();
});
}
QVariant ThreadFetchModel::data(const QModelIndex &idx, int role) const
{
if (idx.row() < 0 || idx.row() > 1) {
return {};
}
if (role == ComponentTypeRole) {
return MessageComponentType::FetchButton;
}
return {};
}
int ThreadFetchModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
const auto threadModel = dynamic_cast<ThreadModel *>(this->parent());
if (threadModel == nullptr) {
qWarning() << "ThreadFetchModel created with incorrect parent, a ThreadModel must be set as the parent on creation.";
return {};
}
return threadModel->moreEventsAvailable({}) ? 1 : 0;
}
QHash<int, QByteArray> ThreadFetchModel::roleNames() const
{
return {
{ComponentTypeRole, "componentType"},
};
}
ThreadChatBarModel::ThreadChatBarModel(QObject *parent, NeoChatRoom *room) ThreadChatBarModel::ThreadChatBarModel(QObject *parent, NeoChatRoom *room)
: QAbstractListModel(parent) : QAbstractListModel(parent)
, m_room(room) , m_room(room)

View File

@@ -19,52 +19,7 @@
#include "messagecontentmodel.h" #include "messagecontentmodel.h"
class NeoChatRoom; class NeoChatRoom;
class ReactionModel;
/**
* @class ThreadFetchModel
*
* A model to provide a fetch more historical messages button in a thread.
*/
class ThreadFetchModel : public QAbstractListModel
{
Q_OBJECT
public:
/**
* @brief Defines the model roles.
*
* The role values need to match MessageContentModel not to blow up.
*
* @sa MessageContentModel
*/
enum Roles {
ComponentTypeRole = MessageContentModel::ComponentTypeRole, /**< The type of component to visualise the message. */
};
Q_ENUM(Roles)
explicit ThreadFetchModel(QObject *parent);
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &idx, int role = Qt::DisplayRole) const override;
/**
* @brief 1 or 0, depending on whether there are more messages to download.
*
* @sa QAbstractItemModel::rowCount
*/
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
/**
* @brief Returns a map with ComponentTypeRole it's the only one.
*
* @sa Roles, QAbstractItemModel::roleNames()
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
};
/** /**
* @class ThreadChatBarModel * @class ThreadChatBarModel
@@ -136,6 +91,11 @@ public:
QString threadRootId() const; QString threadRootId() const;
/**
* @brief The content model for the thread root event.
*/
MessageContentModel *threadRootContentModel() const;
/** /**
* @brief Returns a mapping from Role enum values to role names. * @brief Returns a mapping from Role enum values to role names.
* *
@@ -144,33 +104,34 @@ public:
[[nodiscard]] QHash<int, QByteArray> roleNames() const override; [[nodiscard]] QHash<int, QByteArray> roleNames() const override;
/** /**
* @brief Whether there are more events for the model to fetch. * @brief Whether there is more data available for the model to fetch.
*/
bool moreEventsAvailable(const QModelIndex &parent) const;
/**
* @brief Fetches the next batch of events if any is available.
*/
Q_INVOKABLE void fetchMoreEvents(int max = 5);
/**
* @brief Close the link preview at the given index.
* *
* If the given index is not a link preview component, nothing happens. * @sa QAbstractItemModel::canFetchMore()
*/ */
Q_INVOKABLE void closeLinkPreview(int row); bool canFetchMore(const QModelIndex &parent) const override;
Q_SIGNALS: /**
void moreEventsAvailableChanged(); * @brief Fetches the next batch of model data if any is available.
*
* @sa QAbstractItemModel::fetchMore()
*/
void fetchMore(const QModelIndex &parent) override;
private: private:
QString m_threadRootId; QString m_threadRootId;
QPointer<MessageContentModel> m_threadRootContentModel;
std::deque<QString> m_events; std::unique_ptr<MessageContentModel> m_threadRootContentModel;
ThreadFetchModel *m_threadFetchModel;
std::deque<MessageContentModel *> m_contentModels;
ThreadChatBarModel *m_threadChatBarModel; ThreadChatBarModel *m_threadChatBarModel;
QList<QString> m_events;
QList<QString> m_pendingEvents;
std::unordered_map<QString, std::unique_ptr<Quotient::RoomEvent>> m_unloadedEvents;
QMap<QString, QSharedPointer<ReactionModel>> m_reactionModels;
QPointer<Quotient::GetRelatingEventsWithRelTypeJob> m_currentJob = nullptr; QPointer<Quotient::GetRelatingEventsWithRelTypeJob> m_currentJob = nullptr;
std::optional<QString> m_nextBatch = QString(); std::optional<QString> m_nextBatch = QString();
bool m_addingPending = false; bool m_addingPending = false;

View File

@@ -27,20 +27,24 @@ void TimelineMessageModel::connectNewRoom()
} }
connect(m_room, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) { connect(m_room, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
for (auto &&event : events) {
Q_EMIT newEventAdded(event.get());
}
m_initialized = true; m_initialized = true;
beginInsertRows({}, timelineServerIndex(), timelineServerIndex() + int(events.size()) - 1); beginInsertRows({}, timelineServerIndex(), timelineServerIndex() + int(events.size()) - 1);
}); });
connect(m_room, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) { connect(m_room, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
for (auto &event : events) {
Q_EMIT newEventAdded(event.get());
}
if (rowCount() > 0) {
rowBelowInserted = rowCount() - 1; // See #312
}
m_initialized = true; m_initialized = true;
beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1); beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1);
}); });
connect(m_room, &Room::addedMessages, this, [this](int lowest, int biggest) { connect(m_room, &Room::addedMessages, this, [this](int lowest, int biggest) {
if (m_initialized) { if (m_initialized) {
for (int i = lowest; i == biggest; ++i) {
const auto event = m_room->findInTimeline(i)->event();
Q_EMIT newEventAdded(event);
}
endInsertRows(); endInsertRows();
} }
if (!m_lastReadEventIndex.isValid()) { if (!m_lastReadEventIndex.isValid()) {

View File

@@ -32,6 +32,42 @@ class TimelineMessageModel : public MessageModel
QML_ELEMENT QML_ELEMENT
public: public:
/**
* @brief Defines the model roles.
*/
enum EventRoles {
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
EventIdRole, /**< The matrix event ID of the event. */
TimeRole, /**< The timestamp for when the event was sent (as a QDateTime). */
SectionRole, /**< The date of the event as a string. */
AuthorRole, /**< The author of the event. */
HighlightRole, /**< Whether the event should be highlighted. */
SpecialMarksRole, /**< Whether the event is hidden or not. */
ProgressInfoRole, /**< Progress info when downloading files. */
GenericDisplayRole, /**< A generic string based upon the message type. */
MediaInfoRole, /**< The media info for the event. */
ContentModelRole, /**< The MessageContentModel for the event. */
IsThreadedRole, /**< Whether the message is in a thread. */
ThreadRootRole, /**< The Matrix ID of the thread root message, if any . */
ShowSectionRole, /**< Whether the section header should be shown. */
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
ReactionRole, /**< List model for this event. */
ShowReactionsRole, /**< Whether there are any reactions to be shown. */
VerifiedRole, /**< Whether an encrypted message is sent in a verified session. */
AuthorDisplayNameRole, /**< The displayname for the event's sender; for name change events, the old displayname. */
IsRedactedRole, /**< Whether an event has been deleted. */
IsPendingRole, /**< Whether an event is waiting to be accepted by the server. */
IsEditableRole, /**< Whether the event can be edited by the user. */
LastRole, // Keep this last
};
Q_ENUM(EventRoles)
explicit TimelineMessageModel(QObject *parent = nullptr); explicit TimelineMessageModel(QObject *parent = nullptr);
/** /**

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