Compare commits
41 Commits
work/tobia
...
v25.08.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5a4b2a50a | ||
|
|
17fdad3afd | ||
|
|
64bc2691cb | ||
|
|
c7df34a9c8 | ||
|
|
1525c74b10 | ||
|
|
58b85622c5 | ||
|
|
796470d0e0 | ||
|
|
3a43d99575 | ||
|
|
a62798ef1e | ||
|
|
349d0c5f5f | ||
|
|
c917fc0166 | ||
|
|
a4f2d8fca1 | ||
|
|
fd18f88adf | ||
|
|
546694b08e | ||
|
|
699026fc2f | ||
|
|
c61c2d7437 | ||
|
|
56babbc1c5 | ||
|
|
74b3e703c1 | ||
|
|
f620221113 | ||
|
|
896c001430 | ||
|
|
defee77c96 | ||
|
|
4328ab8e89 | ||
|
|
54b081abba | ||
|
|
29686608e1 | ||
|
|
b720ecf29d | ||
|
|
fc859d679a | ||
|
|
3595ad9293 | ||
|
|
73f8ebc54e | ||
|
|
19cf534acd | ||
|
|
9b86088e26 | ||
|
|
a93117fcd6 | ||
|
|
ee20c90498 | ||
|
|
860a2267d5 | ||
|
|
cb9b2648ca | ||
|
|
1e798b6c15 | ||
|
|
124ffba5e0 | ||
|
|
3aaaa610df | ||
|
|
18e883834c | ||
|
|
6acbd2dffd | ||
|
|
dc5c27aa2d | ||
|
|
26774bbe56 |
@@ -1,2 +0,0 @@
|
||||
[General]
|
||||
disableUnqualifiedAccess = "i18nc,xi18nc,i18ncp,i18n"
|
||||
@@ -20,16 +20,8 @@
|
||||
"--talk-name=org.kde.kwalletd5",
|
||||
"--talk-name=org.kde.StatusNotifierWatcher",
|
||||
"--talk-name=org.freedesktop.secrets",
|
||||
"--talk-name=org.kde.kuiserver",
|
||||
"--own-name=org.kde.StatusNotifierItem-2-2"
|
||||
],
|
||||
"cleanup": [
|
||||
"/include",
|
||||
"/lib/*.a",
|
||||
"/lib/cmake",
|
||||
"/lib/pkgconfig",
|
||||
"/share/ndk-modules"
|
||||
],
|
||||
"modules": [
|
||||
{
|
||||
"name": "kirigamiaddons",
|
||||
@@ -44,22 +36,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "opencv",
|
||||
"config-opts": [
|
||||
"-DBUILD_TESTS=OFF",
|
||||
"-DWITH_GTK=OFF",
|
||||
"-DBUILD_LIST=core,imgproc"
|
||||
],
|
||||
"buildsystem": "cmake-ninja",
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
"url": "https://github.com/opencv/opencv"
|
||||
}
|
||||
],
|
||||
"builddir": true
|
||||
},
|
||||
{
|
||||
"name": "kquickimageeditor",
|
||||
"config-opts": [
|
||||
@@ -106,8 +82,8 @@
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.7.tar.xz",
|
||||
"sha256": "6b452e4750590a2b5617adc40026f28d2f4903de15f1250e1d1c40bfd68ed55e",
|
||||
"url": "https://download.gnome.org/sources/libsecret/0.21/libsecret-0.21.6.tar.xz",
|
||||
"sha256": "747b8c175be108c880d3adfb9c3537ea66e520e4ad2dccf5dce58003aeeca090",
|
||||
"x-checker-data": {
|
||||
"type": "gnome",
|
||||
"name": "libsecret",
|
||||
@@ -177,20 +153,16 @@
|
||||
"name": "kunifiedpush",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"builddir": true,
|
||||
"config-opts": [
|
||||
"-DENABLE_TESTING=OFF",
|
||||
"-DKUNIFIEDPUSH_CLIENT_ONLY=ON"
|
||||
],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
"url": "https://download.kde.org/stable/release-service/25.08.0/src/kunifiedpush-25.08.0.tar.xz",
|
||||
"sha256": "846db6ffc7d93f6afea7ce0d5a9f10b52792157ceb593856542279f4197f3518",
|
||||
"url": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-1.0.0.tar.xz",
|
||||
"sha256": "2ddeba21306d0307114ec50a2c38159ec62359f9fc6cdd58da30a369fbd550cf",
|
||||
"x-checker-data": {
|
||||
"type": "anitya",
|
||||
"project-id": 8763,
|
||||
"project-id": 375055,
|
||||
"stable-only": true,
|
||||
"url-template": "https://download.kde.org/stable/release-service/$version/src/kunifiedpush-$version.tar.xz"
|
||||
"url-template": "https://download.kde.org/stable/kunifiedpush/kunifiedpush-$version.tar.xz"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -43,4 +43,3 @@ Dependencies:
|
||||
Options:
|
||||
per-test-timeout: 90
|
||||
require-passing-tests-on: ['Linux', 'Android', 'FreeBSD', 'Windows']
|
||||
run-qmllint: True
|
||||
|
||||
@@ -8,14 +8,14 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "25")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "08")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "0")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "6.17")
|
||||
set(QT_MIN_VERSION "6.9")
|
||||
set(KF_MIN_VERSION "6.12")
|
||||
set(QT_MIN_VERSION "6.5")
|
||||
|
||||
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
|
||||
|
||||
@@ -107,7 +107,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
|
||||
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
find_package(QuotientQt6 0.9.3)
|
||||
find_package(QuotientQt6 0.9.1)
|
||||
set_package_properties(QuotientQt6 PROPERTIES
|
||||
TYPE REQUIRED
|
||||
DESCRIPTION "Qt wrapper around Matrix API"
|
||||
|
||||
@@ -88,9 +88,3 @@ path = "memorytests/memtest-sync.json"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2024 James Graham <james.h.graham@protonmail.com>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
[[annotations]]
|
||||
path = ".contextProperties.ini"
|
||||
precedence = "aggregate"
|
||||
SPDX-FileCopyrightText = "2025 Tobias Fella <tobias.fella@kde.org>"
|
||||
SPDX-License-Identifier = "BSD-2-Clause"
|
||||
|
||||
@@ -92,15 +92,3 @@ ecm_add_test(
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
||||
TEST_NAME actionstest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
servernoticestest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
||||
TEST_NAME servernoticestest
|
||||
)
|
||||
|
||||
ecm_add_test(
|
||||
roommanagertest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test neochat_server
|
||||
TEST_NAME roommanagertest
|
||||
)
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <QObject>
|
||||
#include <QTest>
|
||||
|
||||
#include <QSignalSpy>
|
||||
#include <Quotient/roommember.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
#include <qtestcase.h>
|
||||
@@ -33,7 +32,6 @@ private Q_SLOTS:
|
||||
void noRoom();
|
||||
void badParent();
|
||||
void reply();
|
||||
void replyMissingUser();
|
||||
void edit();
|
||||
void attachment();
|
||||
};
|
||||
@@ -104,33 +102,6 @@ void ChatBarCacheTest::reply()
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::replyMissingUser()
|
||||
{
|
||||
QScopedPointer<ChatBarCache> chatBarCache(new ChatBarCache(room));
|
||||
chatBarCache->setText(u"some text"_s);
|
||||
chatBarCache->setAttachmentPath(u"some/path"_s);
|
||||
chatBarCache->setReplyId(u"$153456789:example.org"_s);
|
||||
|
||||
QCOMPARE(chatBarCache->text(), u"some text"_s);
|
||||
QCOMPARE(chatBarCache->isReplying(), true);
|
||||
QCOMPARE(chatBarCache->replyId(), u"$153456789:example.org"_s);
|
||||
QCOMPARE(chatBarCache->isEditing(), false);
|
||||
QCOMPARE(chatBarCache->editId(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthor(), room->member(u"@example:example.org"_s));
|
||||
QCOMPARE(chatBarCache->relationMessage(), u"This is an example\ntext message"_s);
|
||||
QCOMPARE(chatBarCache->attachmentPath(), QString());
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), true);
|
||||
|
||||
QSignalSpy relationAuthorIsPresentSpy(chatBarCache.get(), &ChatBarCache::relationAuthorIsPresentChanged);
|
||||
|
||||
// sync again, which will simulate the reply user leaving the room
|
||||
room->syncNewEvents(u"test-min-sync-extra-sync.json"_s);
|
||||
|
||||
QTRY_COMPARE(relationAuthorIsPresentSpy.count(), 1);
|
||||
QCOMPARE(chatBarCache->relationAuthorIsPresent(), false);
|
||||
}
|
||||
|
||||
void ChatBarCacheTest::edit()
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
{
|
||||
"state": {
|
||||
"events": [
|
||||
{
|
||||
"content": {
|
||||
"membership": "leave"
|
||||
},
|
||||
"event_id": "$1432735824666PhrSA:example.org",
|
||||
"origin_server_ts": 1432735824666,
|
||||
"room_id": "!jEsUZKDJdhlrceRyVU:example.org",
|
||||
"sender": "@example:example.org",
|
||||
"state_key": "@example:example.org",
|
||||
"type": "m.room.member",
|
||||
"unsigned": {
|
||||
"replaces_state": "$143273582443PhrSn:example.org"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@
|
||||
#include <Quotient/roommember.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "models/eventmessagecontentmodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "testutils.h"
|
||||
@@ -39,17 +39,17 @@ void MessageContentModelTest::initTestCase()
|
||||
void MessageContentModelTest::missingEvent()
|
||||
{
|
||||
auto room = new TestUtils::TestRoom(connection, u"#firstRoom:kde.org"_s);
|
||||
auto model1 = EventMessageContentModel(room, u"$153456789:example.org"_s);
|
||||
auto model1 = MessageContentModel(room, u"$153456789:example.org"_s);
|
||||
|
||||
QCOMPARE(model1.rowCount(), 1);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading…"_s);
|
||||
QCOMPARE(model1.data(model1.index(0), MessageContentModel::DisplayRole), u"Loading"_s);
|
||||
|
||||
auto model2 = EventMessageContentModel(room, u"$153456789:example.org"_s, true);
|
||||
auto model2 = MessageContentModel(room, u"$153456789:example.org"_s, true);
|
||||
|
||||
QCOMPARE(model2.rowCount(), 1);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::ComponentTypeRole), MessageComponentType::Loading);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply…"_s);
|
||||
QCOMPARE(model2.data(model2.index(0), MessageContentModel::DisplayRole), u"Loading reply"_s);
|
||||
|
||||
room->syncNewEvents(u"test-min-sync.json"_s);
|
||||
QCOMPARE(model1.rowCount(), 2);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
|
||||
#include "models/eventmessagecontentmodel.h"
|
||||
#include "models/messagecontentmodel.h"
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -21,7 +21,7 @@ class ReactionModelTest : public QObject
|
||||
private:
|
||||
Connection *connection = nullptr;
|
||||
TestUtils::TestRoom *room = nullptr;
|
||||
EventMessageContentModel *parentModel;
|
||||
MessageContentModel *parentModel;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
@@ -34,7 +34,7 @@ void ReactionModelTest::initTestCase()
|
||||
{
|
||||
connection = Connection::makeMockConnection(u"@bob:kde.org"_s);
|
||||
room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-reactionmodel-sync.json"_s);
|
||||
parentModel = new EventMessageContentModel(room, "123456"_L1);
|
||||
parentModel = new MessageContentModel(room, "123456"_L1);
|
||||
}
|
||||
|
||||
void ReactionModelTest::basicReaction()
|
||||
|
||||
@@ -1,141 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
#include <QVariantList>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "roommanager.h"
|
||||
|
||||
#include "server.h"
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class RoomManagerTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
NeoChatConnection *connection = nullptr;
|
||||
NeoChatRoom *room = nullptr;
|
||||
|
||||
Server server;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void testMaximizeMedia();
|
||||
void testResolveMatrixLinks();
|
||||
};
|
||||
|
||||
void RoomManagerTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
QVERIFY(connection);
|
||||
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
RoomManager::instance().setConnection(connection);
|
||||
QSignalSpy roomSpy(&RoomManager::instance(), &RoomManager::currentRoomChanged);
|
||||
RoomManager::instance().resolveResource(room->id());
|
||||
QVERIFY(roomSpy.size() > 0);
|
||||
}
|
||||
|
||||
void RoomManagerTest::testMaximizeMedia()
|
||||
{
|
||||
QSignalSpy spy(&RoomManager::instance(), &RoomManager::showMaximizedMedia);
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Tried to open media for empty event id");
|
||||
RoomManager::instance().maximizeMedia(QString());
|
||||
QVERIFY(!spy.wait(10));
|
||||
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, "Tried to open media for unknown event id \"Doesn't exist\"");
|
||||
RoomManager::instance().maximizeMedia(u"Doesn't exist"_s);
|
||||
QVERIFY(!spy.wait(10));
|
||||
|
||||
const auto eventWithoutMedia = server.sendEvent(room->id(),
|
||||
u"m.room.message"_s,
|
||||
QJsonObject({
|
||||
{u"body"_s, u"Foo"_s},
|
||||
{u"format"_s, u"org.matrix.custom.html"_s},
|
||||
{u"formatted_body"_s, u"Foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
}));
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, u"Tried to open media for unknown event id \"%1\""_s.arg(eventWithoutMedia).toLatin1().data());
|
||||
RoomManager::instance().maximizeMedia(eventWithoutMedia);
|
||||
QVERIFY(!spy.wait(10));
|
||||
|
||||
// NOTE: This is supposed to test that maximizing pending media works correctly. This probably doesn't work in the UI yet, but at least the backend supports
|
||||
// it. If the server ever learns how to process events, this becomes pointless and we need to find a way of preventing *these* events from arriving
|
||||
auto pendingEventWithoutMedia = room->postText(u"Hello"_s);
|
||||
QTest::ignoreMessage(QtMsgType::QtWarningMsg, u"Tried to open media for unknown event id \"%1\""_s.arg(pendingEventWithoutMedia).toLatin1().data());
|
||||
RoomManager::instance().maximizeMedia(pendingEventWithoutMedia);
|
||||
QVERIFY(!spy.wait(10));
|
||||
|
||||
const auto eventWithMedia = server.sendEvent(room->id(),
|
||||
u"m.room.message"_s,
|
||||
QJsonObject({
|
||||
{u"body"_s, u"Foo"_s},
|
||||
{u"filename"_s, u"foo.jpg"_s},
|
||||
{u"info"_s,
|
||||
QJsonObject{
|
||||
{u"h"_s, 1000},
|
||||
{u"w"_s, 2000},
|
||||
{u"size"_s, 10000},
|
||||
{u"mimetype"_s, u"image/png"_s},
|
||||
}},
|
||||
{u"msgtype"_s, u"m.image"_s},
|
||||
{u"url"_s, u"mxc://foo.bar/asdf"_s},
|
||||
}));
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
RoomManager::instance().maximizeMedia(eventWithMedia);
|
||||
QVERIFY(spy.size() == 1);
|
||||
QVERIFY(spy[0][0] == 0);
|
||||
|
||||
auto pendingEventWithMedia = room->postJson(u"m.room.message"_s,
|
||||
QJsonObject({
|
||||
{u"body"_s, u"Foo"_s},
|
||||
{u"filename"_s, u"foo.jpg"_s},
|
||||
{u"info"_s,
|
||||
QJsonObject{
|
||||
{u"h"_s, 1000},
|
||||
{u"w"_s, 2000},
|
||||
{u"size"_s, 10000},
|
||||
{u"mimetype"_s, u"image/png"_s},
|
||||
}},
|
||||
{u"msgtype"_s, u"m.image"_s},
|
||||
{u"url"_s, u"mxc://foo.bar/asdf"_s},
|
||||
}));
|
||||
RoomManager::instance().maximizeMedia(pendingEventWithMedia);
|
||||
QVERIFY(spy.size() == 2);
|
||||
QVERIFY(spy[1][0] == 0);
|
||||
}
|
||||
|
||||
void RoomManagerTest::testResolveMatrixLinks()
|
||||
{
|
||||
// Test if resolving a non-joined room will bring up the confirmation dialog.
|
||||
const QSignalSpy askToJoinSpy(&RoomManager::instance(), &RoomManager::askJoinRoom);
|
||||
RoomManager::instance().resolveResource(QStringLiteral("matrix:r/testbuild:matrix.org"), QStringLiteral("join"));
|
||||
QTRY_COMPARE(askToJoinSpy.size(), 1);
|
||||
}
|
||||
|
||||
QTEST_MAIN(RoomManagerTest)
|
||||
#include "roommanagertest.moc"
|
||||
@@ -109,20 +109,98 @@ void Server::start()
|
||||
m_server.route(u"/_matrix/client/v3/rooms/<arg>/invite"_s,
|
||||
QHttpServerRequest::Method::Post,
|
||||
[this](const QString &roomId, QHttpServerResponder &responder, const QHttpServerRequest &request) {
|
||||
Changes changes;
|
||||
changes.invitations += Changes::InviteUser{
|
||||
.userId = QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString(),
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_invitedUsers[roomId] += QJsonDocument::fromJson(request.body()).object()[u"user_id"_s].toString();
|
||||
responder.write(QJsonDocument(QJsonObject{}), QHttpServerResponder::StatusCode::Ok);
|
||||
});
|
||||
|
||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, this, &Server::sync);
|
||||
m_server.route(u"/_matrix/client/r0/sync"_s, QHttpServerRequest::Method::Get, [this](QHttpServerResponder &responder) {
|
||||
QMap<QString, QJsonArray> stateEvents;
|
||||
|
||||
for (const auto &[roomId, matrixId] : m_roomsToCreate) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, matrixId},
|
||||
{u"state_key"_s, QString()},
|
||||
{u"type"_s, u"m.room.create"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, matrixId},
|
||||
{u"state_key"_s, matrixId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
m_roomsToCreate.clear();
|
||||
for (const auto &roomId : m_invitedUsers.keys()) {
|
||||
const auto &values = m_invitedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_invitedUsers.clear();
|
||||
|
||||
for (const auto &roomId : m_bannedUsers.keys()) {
|
||||
const auto &values = m_bannedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_bannedUsers.clear();
|
||||
|
||||
for (const auto &roomId : m_joinedUsers.keys()) {
|
||||
const auto &values = m_joinedUsers[roomId];
|
||||
for (const auto &value : values) {
|
||||
stateEvents[roomId] += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, value},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
m_joinedUsers.clear();
|
||||
|
||||
QJsonObject rooms;
|
||||
for (const auto &roomId : stateEvents.keys()) {
|
||||
rooms[roomId] = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents[roomId]}}}};
|
||||
}
|
||||
|
||||
responder.write(QJsonDocument(QJsonObject{{u"rooms"_s, QJsonObject{{u"join"_s, rooms}}}}), QHttpServerResponder::StatusCode::Ok);
|
||||
});
|
||||
|
||||
QSslConfiguration config;
|
||||
QFile key(QStringLiteral(DATA_DIR) + u"/localhost.key"_s);
|
||||
void(key.open(QFile::ReadOnly));
|
||||
key.open(QFile::ReadOnly);
|
||||
config.setPrivateKey(QSslKey(&key, QSsl::Rsa));
|
||||
config.setLocalCertificate(QSslCertificate::fromPath(QStringLiteral(DATA_DIR) + u"/localhost.crt"_s).front());
|
||||
m_sslServer.setSslConfiguration(config);
|
||||
@@ -136,239 +214,22 @@ void Server::start()
|
||||
|
||||
QString Server::createRoom(const QString &matrixId)
|
||||
{
|
||||
const auto roomId = generateRoomId();
|
||||
Changes changes;
|
||||
changes.newRooms += Changes::NewRoom{
|
||||
.initialMembers = {matrixId},
|
||||
.roomId = {roomId},
|
||||
.tags = {},
|
||||
};
|
||||
m_state += changes;
|
||||
auto roomId = generateRoomId();
|
||||
m_roomsToCreate += {roomId, matrixId};
|
||||
return roomId;
|
||||
}
|
||||
|
||||
void Server::inviteUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.invitations += Changes::InviteUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_invitedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
void Server::banUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.bans += Changes::BanUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
m_bannedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
void Server::joinUser(const QString &roomId, const QString &matrixId)
|
||||
{
|
||||
Changes changes;
|
||||
changes.joins += Changes::JoinUser{
|
||||
.userId = matrixId,
|
||||
.roomId = roomId,
|
||||
};
|
||||
m_state += changes;
|
||||
}
|
||||
|
||||
QString Server::createServerNoticesRoom(const QString &matrixId)
|
||||
{
|
||||
const auto roomId = generateRoomId();
|
||||
Changes changes;
|
||||
changes.newRooms += Changes::NewRoom{
|
||||
.initialMembers = {matrixId},
|
||||
.roomId = {roomId},
|
||||
.tags = {u"m.server_notice"_s},
|
||||
};
|
||||
m_state += changes;
|
||||
return roomId;
|
||||
}
|
||||
|
||||
QString Server::sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content)
|
||||
{
|
||||
Changes changes;
|
||||
const auto eventId = generateEventId();
|
||||
changes.events += Changes::Event{
|
||||
.fullJson = QJsonObject{{u"type"_s, eventType},
|
||||
{u"content"_s, content},
|
||||
{u"sender"_s, u"@foo:server.com"_s},
|
||||
{u"event_id"_s, eventId},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, roomId}},
|
||||
};
|
||||
m_state += changes;
|
||||
return eventId;
|
||||
}
|
||||
|
||||
void Server::sync(const QHttpServerRequest &request, QHttpServerResponder &responder)
|
||||
{
|
||||
QJsonObject joinRooms;
|
||||
auto token = request.query().queryItemValue(u"since"_s).toInt();
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &newRoom : change.newRooms) {
|
||||
QJsonArray stateEvents;
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"room_version"_s, u"11"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, newRoom.roomId},
|
||||
{u"sender"_s, newRoom.initialMembers[0]},
|
||||
{u"state_key"_s, QString()},
|
||||
{u"type"_s, u"m.room.create"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
for (const auto &member : newRoom.initialMembers) {
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, newRoom.roomId},
|
||||
{u"sender"_s, member},
|
||||
{u"state_key"_s, member},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
}
|
||||
|
||||
auto room = QJsonObject{{u"state"_s, QJsonObject{{u"events"_s, stateEvents}}}};
|
||||
|
||||
QJsonArray roomAccountData;
|
||||
QJsonObject tags;
|
||||
for (const auto &tag : newRoom.tags) {
|
||||
tags[tag] = QJsonObject();
|
||||
}
|
||||
if (!tags.empty()) {
|
||||
roomAccountData += QJsonObject{{u"type"_s, u"m.tag"_s}, {u"content"_s, QJsonObject{{u"tags"_s, tags}}}};
|
||||
}
|
||||
|
||||
if (roomAccountData.size() > 0) {
|
||||
room[u"account_data"] = QJsonObject{{u"events"_s, roomAccountData}};
|
||||
}
|
||||
|
||||
joinRooms[newRoom.roomId] = room;
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &invitation : change.invitations) {
|
||||
// TODO: The invitation could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[invitation.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"invite"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, invitation.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, invitation.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(invitation.roomId)) {
|
||||
auto room = joinRooms[invitation.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[invitation.roomId] = room;
|
||||
} else {
|
||||
joinRooms[invitation.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &ban : change.bans) {
|
||||
// TODO: The ban could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[ban.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"ban"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, ban.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, ban.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(ban.roomId)) {
|
||||
auto room = joinRooms[ban.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[ban.roomId] = room;
|
||||
} else {
|
||||
joinRooms[ban.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &join : change.joins) {
|
||||
// TODO: The join could be for a room we haven't joined yet. Shouldn't be necessary for now, though.
|
||||
auto stateEvents = joinRooms[join.roomId][u"state"_s][u"events"_s].toArray();
|
||||
stateEvents += QJsonObject{
|
||||
{u"content"_s, QJsonObject{{u"displayname"_s, u"User"_s}, {u"membership"_s, u"join"_s}}},
|
||||
{u"event_id"_s, generateEventId()},
|
||||
{u"origin_server_ts"_s, QDateTime::currentMSecsSinceEpoch()},
|
||||
{u"room_id"_s, join.roomId},
|
||||
{u"sender"_s, u"@user:localhost:1234"_s},
|
||||
{u"state_key"_s, join.userId},
|
||||
{u"type"_s, u"m.room.member"_s},
|
||||
{u"unsigned"_s, QJsonObject{{u"age"_s, 1234}}},
|
||||
};
|
||||
if (joinRooms.contains(join.roomId)) {
|
||||
auto room = joinRooms[join.roomId].toObject();
|
||||
room[u"state"_s] = QJsonObject{{u"events"_s, stateEvents}};
|
||||
joinRooms[join.roomId] = room;
|
||||
} else {
|
||||
joinRooms[join.roomId] = QJsonObject{{u"state"_s,
|
||||
QJsonObject{
|
||||
{u"events"_s, stateEvents},
|
||||
}}};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto &change : m_state.mid(token)) {
|
||||
for (const auto &event : change.events) {
|
||||
// TODO the room might be in a different join state.
|
||||
auto timeline = joinRooms[event.fullJson[u"room_id"_s].toString()][u"timeline"_s][u"events"_s].toArray();
|
||||
timeline += event.fullJson;
|
||||
if (joinRooms.contains(event.fullJson[u"room_id"_s].toString())) {
|
||||
auto room = joinRooms[event.fullJson[u"room_id"_s].toString()].toObject();
|
||||
room[u"timeline"_s] = QJsonObject{{u"events"_s, timeline}};
|
||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = room;
|
||||
} else {
|
||||
joinRooms[event.fullJson[u"room_id"_s].toString()] = QJsonObject{
|
||||
{u"timeline"_s, QJsonObject{{u"events"_s, timeline}}},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QJsonObject syncData = {
|
||||
// {u"account_data"_s, QJsonObject {}},
|
||||
// {u"presence"_s, QJsonObject {}},
|
||||
{u"next_batch"_s, QString::number(m_state.size())},
|
||||
};
|
||||
|
||||
QJsonObject rooms;
|
||||
if (!joinRooms.isEmpty()) {
|
||||
rooms[u"join"_s] = joinRooms;
|
||||
}
|
||||
|
||||
if (!rooms.empty()) {
|
||||
syncData[u"rooms"_s] = rooms;
|
||||
}
|
||||
|
||||
qWarning() << syncData;
|
||||
responder.write(QJsonDocument(syncData), QHttpServerResponder::StatusCode::Ok);
|
||||
m_joinedUsers[roomId] += matrixId;
|
||||
}
|
||||
|
||||
@@ -2,51 +2,10 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include <QHttpServer>
|
||||
#include <QJsonObject>
|
||||
#include <QSslServer>
|
||||
|
||||
struct Changes {
|
||||
struct NewRoom {
|
||||
QStringList initialMembers;
|
||||
QString roomId;
|
||||
QStringList tags;
|
||||
};
|
||||
QList<NewRoom> newRooms;
|
||||
|
||||
struct InviteUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<InviteUser> invitations;
|
||||
|
||||
struct BanUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<BanUser> bans;
|
||||
|
||||
struct JoinUser {
|
||||
QString userId;
|
||||
QString roomId;
|
||||
};
|
||||
QList<JoinUser> joins;
|
||||
|
||||
struct Event {
|
||||
QJsonObject fullJson;
|
||||
};
|
||||
QList<Event> events;
|
||||
};
|
||||
|
||||
struct RoomData {
|
||||
QStringList members;
|
||||
QString id;
|
||||
QStringList tags;
|
||||
};
|
||||
|
||||
class Server : public QObject
|
||||
class Server
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
Server();
|
||||
|
||||
@@ -62,17 +21,13 @@ public:
|
||||
void banUser(const QString &roomId, const QString &matrixId);
|
||||
void joinUser(const QString &roomId, const QString &matrixId);
|
||||
|
||||
/**
|
||||
* Create a server notices room.
|
||||
*/
|
||||
QString createServerNoticesRoom(const QString &matrixId);
|
||||
QString sendEvent(const QString &roomId, const QString &eventType, const QJsonObject &content);
|
||||
|
||||
private:
|
||||
QHttpServer m_server;
|
||||
QSslServer m_sslServer;
|
||||
|
||||
void sync(const QHttpServerRequest &request, QHttpServerResponder &responder);
|
||||
QHash<QString, QList<QString>> m_invitedUsers;
|
||||
QHash<QString, QList<QString>> m_bannedUsers;
|
||||
QHash<QString, QList<QString>> m_joinedUsers;
|
||||
|
||||
QList<Changes> m_state;
|
||||
QList<std::pair<QString, QString>> m_roomsToCreate;
|
||||
};
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QObject>
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/eventstats.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
#include "server.h"
|
||||
|
||||
#include "testutils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class ServerNoticesTest : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
NeoChatConnection *connection = nullptr;
|
||||
Server server;
|
||||
|
||||
private Q_SLOTS:
|
||||
void initTestCase();
|
||||
void test();
|
||||
};
|
||||
|
||||
void ServerNoticesTest::initTestCase()
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
server.start();
|
||||
KLocalizedString::setApplicationDomain(QByteArrayLiteral("neochat"));
|
||||
auto accountManager = new AccountManager(true);
|
||||
QSignalSpy spy(accountManager, &AccountManager::connectionAdded);
|
||||
connection = dynamic_cast<NeoChatConnection *>(accountManager->accounts()->front());
|
||||
QVERIFY(connection);
|
||||
auto roomId = server.createRoom(u"@user:localhost:1234"_s);
|
||||
RoomManager::instance().setConnection(connection);
|
||||
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
// We need to wait for two syncs, as the next one won't have the changes yet
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
auto room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(room);
|
||||
}
|
||||
|
||||
void ServerNoticesTest::test()
|
||||
{
|
||||
auto roomTreeModel = RoomManager::instance().roomTreeModel();
|
||||
QCOMPARE(roomTreeModel->rowCount(roomTreeModel->index(NeoChatRoomType::ServerNotice, 0)), 0);
|
||||
auto sortFilterRoomTreeModel = RoomManager::instance().sortFilterRoomTreeModel();
|
||||
const auto roomId = server.createServerNoticesRoom(u"@user:localhost:1234"_s);
|
||||
QSignalSpy syncSpy(connection, &Connection::syncDone);
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
const auto room = dynamic_cast<NeoChatRoom *>(connection->room(roomId));
|
||||
QVERIFY(connection->room(roomId)->isServerNoticeRoom());
|
||||
QCOMPARE(roomTreeModel->rowCount(roomTreeModel->index(NeoChatRoomType::ServerNotice, 0)), 1);
|
||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 1 /* Below the normal room */);
|
||||
server.sendEvent(roomId,
|
||||
u"m.room.message"_s,
|
||||
QJsonObject{
|
||||
{u"body"_s, u"Foo"_s},
|
||||
{u"format"_s, u"org.matrix.custom.html"_s},
|
||||
{u"formatted_body"_s, u"Foo"_s},
|
||||
{u"msgtype"_s, u"m.text"_s},
|
||||
});
|
||||
QVERIFY(syncSpy.wait());
|
||||
QVERIFY(syncSpy.wait());
|
||||
sortFilterRoomTreeModel->invalidate();
|
||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 0);
|
||||
room->markAllMessagesAsRead();
|
||||
QCOMPARE(sortFilterRoomTreeModel->mapFromSource(roomTreeModel->indexForRoom(room)).parent().row(), 1 /* Below the normal room */);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(ServerNoticesTest)
|
||||
#include "servernoticestest.moc"
|
||||
@@ -1,7 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include <QTest>
|
||||
#include <Quotient/events/event.h>
|
||||
#include <Quotient/syncdata.h>
|
||||
|
||||
@@ -33,7 +32,7 @@ public:
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
Q_UNUSED(testSyncFile.open(QIODevice::ReadOnly));
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
const auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
Quotient::SyncRoomData roomData(id(), Quotient::JoinState::Join, testSyncJson.object());
|
||||
update(std::move(roomData));
|
||||
@@ -47,7 +46,7 @@ inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFile
|
||||
if (!eventFileName.isEmpty()) {
|
||||
QFile testEventFile;
|
||||
testEventFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + eventFileName);
|
||||
Q_UNUSED(testEventFile.open(QIODevice::ReadOnly));
|
||||
testEventFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
|
||||
return Quotient::loadEvent<EventT>(testSyncJson);
|
||||
}
|
||||
|
||||
@@ -34,10 +34,6 @@ private Q_SLOTS:
|
||||
void stripDisallowedTags();
|
||||
void stripDisallowedAttributes();
|
||||
void emptyCodeTags();
|
||||
void addStyle_data();
|
||||
void addStyle();
|
||||
void dontAddStyle_data();
|
||||
void dontAddStyle();
|
||||
|
||||
void sendSimpleStringCase();
|
||||
void sendSingleParaMarkup();
|
||||
@@ -75,9 +71,6 @@ private Q_SLOTS:
|
||||
|
||||
void componentOutput_data();
|
||||
void componentOutput();
|
||||
|
||||
void updateSpoiler_data();
|
||||
void updateSpoiler();
|
||||
};
|
||||
|
||||
void TextHandlerTest::initTestCase()
|
||||
@@ -96,26 +89,21 @@ void TextHandlerTest::initTestCase()
|
||||
|
||||
void TextHandlerTest::allowedAttributes()
|
||||
{
|
||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
const QString testInputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||
const QString testOutputString1S = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||
const QString testOutputString1R = u"<span data-mx-spoiler style=\"color: transparent; background: %1;\"><font color=#FFFFFF>Test</font><span>"_s.arg(
|
||||
theme->alternateBackgroundColor().name());
|
||||
const QString testOutputString1 = u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||
// Handle urls where the href has either single (') or double (") quotes.
|
||||
const QString testInputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
||||
const QString testOutputString2S = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
||||
const QString testOutputString2R =
|
||||
u"<a href=\"https://kde.org\" style=\"text-decoration: none;\">link</a><a href='https://kde.org' style=\"text-decoration: none;\">link</a>"_s;
|
||||
const QString testOutputString2 = u"<a href=\"https://kde.org\">link</a><a href='https://kde.org'>link</a>"_s;
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString1);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString1S);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1R);
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString1);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString1);
|
||||
|
||||
testTextHandler.setData(testInputString2);
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString2S);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2R);
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString2);
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString2);
|
||||
}
|
||||
|
||||
void TextHandlerTest::stripDisallowedTags()
|
||||
@@ -158,56 +146,6 @@ void TextHandlerTest::emptyCodeTags()
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::addStyle_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("link") << u"<a href=\"https://kde.org\">link</a>"_s << u"<a href=\"https://kde.org\" style=\"text-decoration: none;\">link</a>"_s;
|
||||
QTest::newRow("table")
|
||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s
|
||||
<< u"<table style=\"width: 100%; border-collapse: collapse; border: 1px; border-style: solid;\"><tr><th style=\"border: 1px solid black; padding: 3px;\">Company</th><th style=\"border: 1px solid black; padding: 3px;\">Contact</th><th style=\"border: 1px solid black; padding: 3px;\">Country</th></tr><tr><td style=\"border: 1px solid black; padding: 3px;\">Alfreds Futterkiste</td><td style=\"border: 1px solid black; padding: 3px;\">Maria Anders</td><td style=\"border: 1px solid black; padding: 3px;\">Germany</td></tr><tr><td style=\"border: 1px solid black; padding: 3px;\">Centro comercial Moctezuma</td><td style=\"border: 1px solid black; padding: 3px;\">Francisco Chang</td><td style=\"border: 1px solid black; padding: 3px;\">Mexico</td></tr></table>"_s;
|
||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
QTest::newRow("spoiler") << u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s
|
||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\"><font color=#FFFFFF>Test</font><span>"_s.arg(
|
||||
theme->alternateBackgroundColor().name());
|
||||
}
|
||||
|
||||
void TextHandlerTest::addStyle()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::dontAddStyle_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
|
||||
QTest::newRow("link") << u"<a href=\"https://kde.org\">link</a>"_s << u"<a href=\"https://kde.org\">link</a>"_s;
|
||||
QTest::newRow("table")
|
||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s
|
||||
<< u"<table><tr><th>Company</th><th>Contact</th><th>Country</th></tr><tr><td>Alfreds Futterkiste</td><td>Maria Anders</td><td>Germany</td></tr><tr><td>Centro comercial Moctezuma</td><td>Francisco Chang</td><td>Mexico</td></tr></table>"_s;
|
||||
QTest::newRow("spoiler") << u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s
|
||||
<< u"<span data-mx-spoiler><font color=#FFFFFF>Test</font><span>"_s;
|
||||
}
|
||||
|
||||
void TextHandlerTest::dontAddStyle()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
|
||||
QCOMPARE(testTextHandler.handleSendText(), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::sendSimpleStringCase()
|
||||
{
|
||||
const QString testInputString = u"This data should just be left alone."_s;
|
||||
@@ -400,8 +338,7 @@ void TextHandlerTest::receiveRichInPlainOut()
|
||||
void TextHandlerTest::receivePlainTextIn()
|
||||
{
|
||||
const QString testInputString = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
|
||||
const QString testOutputStringRich =
|
||||
u"<plain text in tag bracket><br>Test link <a href=\"https://kde.org\" style=\"text-decoration: none;\">https://kde.org</a>."_s;
|
||||
const QString testOutputStringRich = u"<plain text in tag bracket><br>Test link <a href=\"https://kde.org\">https://kde.org</a>."_s;
|
||||
QString testOutputStringPlain = u"<plain text in tag bracket>\nTest link https://kde.org."_s;
|
||||
|
||||
// Make sure quotes are maintained in a plain string.
|
||||
@@ -471,7 +408,7 @@ void TextHandlerTest::receivePlainStripMarkup()
|
||||
void TextHandlerTest::receiveRichUserPill()
|
||||
{
|
||||
const QString testInputString = u"<p><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></p>"_s;
|
||||
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\" style=\"text-decoration: none;\">@alice:example.org</a></b>"_s;
|
||||
const QString testOutputString = u"<b><a href=\"https://matrix.to/#/@alice:example.org\">@alice:example.org</a></b>"_s;
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -523,23 +460,21 @@ void TextHandlerTest::receiveRichPlainUrl_data()
|
||||
// so we can confirm consistent behaviour for complex urls.
|
||||
QTest::addRow("link 1")
|
||||
<< u"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">Link already rich</a>"_s
|
||||
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\" style=\"text-decoration: none;\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\" style=\"text-decoration: none;\">Link already rich</a>"_s;
|
||||
<< u"<a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im</a> <a href=\"https://matrix.to/#/!RvzunyTWZGfNxJVQqv:matrix.org/$-9TJVTh5PvW6MvIhFDwteiyLBVGriinueO5eeIazQS8?via=libera.chat&via=matrix.org&via=fedora.im\">Link already rich</a>"_s;
|
||||
|
||||
// Another real case. The linkification wasn't handling it when a single link
|
||||
// contains what looks like and email. It was broken into 3 but needs to
|
||||
// be just single link.
|
||||
QTest::addRow("link 2")
|
||||
<< u"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/"_s
|
||||
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\" style=\"text-decoration: none;\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
|
||||
<< u"<a href=\"https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/\">https://lore.kernel.org/lkml/CAHk-=wio46vC4t6xXD-sFqjoPwFm_u515jm3suzmkGxQTeA1_A@mail.gmail.com/</a>"_s;
|
||||
|
||||
QTest::addRow("email")
|
||||
<< uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
|
||||
<< uR"(<a href="mailto:email@example.com" style="text-decoration: none;">email@example.com</a> <a href="mailto:email@example.com" style="text-decoration: none;">Link already rich</a>)"_s;
|
||||
QTest::addRow("email") << uR"(email@example.com <a href="mailto:email@example.com">Link already rich</a>)"_s
|
||||
<< uR"(<a href="mailto:email@example.com">email@example.com</a> <a href="mailto:email@example.com">Link already rich</a>)"_s;
|
||||
QTest::addRow("mxid")
|
||||
<< u"@user:kde.org <a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a>"_s
|
||||
<< u"<b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">Link already rich</a></b>"_s;
|
||||
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s
|
||||
<< u"a <b><a href=\"https://matrix.to/#/@user:kde.org\" style=\"text-decoration: none;\">@user:kde.org</a></b> b"_s;
|
||||
<< u"<b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> <b><a href=\"https://matrix.to/#/@user:kde.org\">Link already rich</a></b>"_s;
|
||||
QTest::addRow("mxid with prefix") << u"a @user:kde.org b"_s << u"a <b><a href=\"https://matrix.to/#/@user:kde.org\">@user:kde.org</a></b> b"_s;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -661,35 +596,5 @@ void TextHandlerTest::componentOutput()
|
||||
QCOMPARE(testTextHandler.textComponents(testInputString), testOutputComponents);
|
||||
}
|
||||
|
||||
void TextHandlerTest::updateSpoiler_data()
|
||||
{
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QString>("testOutputString");
|
||||
QTest::addColumn<bool>("spoilerRevealed");
|
||||
|
||||
auto theme = static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true));
|
||||
QTest::newRow("same length") << u"<span data-mx-spoiler style=\"color: #123456; background: #123456;\">Test<span>"_s
|
||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(
|
||||
theme->alternateBackgroundColor().name())
|
||||
<< false;
|
||||
QTest::newRow("different length") << u"<span data-mx-spoiler style=\"color: short; background: looooooooooong;\">Test<span>"_s
|
||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(
|
||||
theme->alternateBackgroundColor().name())
|
||||
<< false;
|
||||
QTest::newRow("spoiler revealed")
|
||||
<< u"<span data-mx-spoiler style=\"color: transparent; background: %1;\">Test<span>"_s.arg(theme->alternateBackgroundColor().name())
|
||||
<< u"<span data-mx-spoiler style=\"color: %1; background: %2;\">Test<span>"_s.arg(theme->textColor().name(), theme->alternateBackgroundColor().name())
|
||||
<< true;
|
||||
}
|
||||
|
||||
void TextHandlerTest::updateSpoiler()
|
||||
{
|
||||
QFETCH(QString, testInputString);
|
||||
QFETCH(QString, testOutputString);
|
||||
QFETCH(bool, spoilerRevealed);
|
||||
|
||||
QCOMPARE(TextHandler::updateSpoilerText(this, testInputString, spoilerRevealed), testOutputString);
|
||||
}
|
||||
|
||||
QTEST_MAIN(TextHandlerTest)
|
||||
#include "texthandlertest.moc"
|
||||
|
||||
@@ -161,7 +161,7 @@ void TimelineMessageModelTest::pendingEvent()
|
||||
// different every time.
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + u"test-pending-sync.json"_s);
|
||||
QVERIFY(testSyncFile.open(QIODevice::ReadOnly));
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll());
|
||||
auto root = testSyncJson.object();
|
||||
auto timeline = root["timeline"_L1].toObject();
|
||||
|
||||
@@ -3,12 +3,12 @@
|
||||
|
||||
add_definitions(-DDATA_DIR="${CMAKE_CURRENT_SOURCE_DIR}" )
|
||||
|
||||
qt_add_executable(timeline_memtest
|
||||
qt_add_executable(timeline-memtest
|
||||
main.cpp
|
||||
)
|
||||
|
||||
target_link_libraries(timeline_memtest PRIVATE neochatplugin Timelineplugin)
|
||||
target_link_libraries(timeline_memtest PUBLIC
|
||||
target_link_libraries(timeline-memtest PRIVATE neochatplugin Timelineplugin)
|
||||
target_link_libraries(timeline-memtest PUBLIC
|
||||
Qt::Core
|
||||
Qt::Quick
|
||||
Qt::Qml
|
||||
@@ -16,13 +16,14 @@ target_link_libraries(timeline_memtest PUBLIC
|
||||
Qt::QuickControls2
|
||||
Qt::Widgets
|
||||
KF6::I18nQml
|
||||
KF6::Kirigami
|
||||
QuotientQt6
|
||||
LibNeoChat
|
||||
Timeline
|
||||
)
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
@@ -28,7 +28,7 @@ int main(int argc, char **argv)
|
||||
engine.rootContext()->setContextProperty(u"memTestTimelineModel"_s, memTestTimelineModel);
|
||||
engine.rootContext()->setContextProperty(u"messageFilterModel"_s, messageFilterModel);
|
||||
|
||||
engine.loadFromModule("org.kde.neochat.timeline_memtest", "Main");
|
||||
engine.loadFromModule("org.kde.neochat.timeline-memtest", "Main");
|
||||
|
||||
return app.exec();
|
||||
}
|
||||
|
||||
@@ -37,11 +37,7 @@ public:
|
||||
if (!syncFileName.isEmpty()) {
|
||||
QFile testSyncFile;
|
||||
testSyncFile.setFileName(QStringLiteral(DATA_DIR) + u'/' + syncFileName);
|
||||
auto ok = testSyncFile.open(QIODevice::ReadOnly);
|
||||
if (!ok) {
|
||||
qWarning() << "Failed to open" << testSyncFile.fileName() << testSyncFile.errorString();
|
||||
}
|
||||
|
||||
testSyncFile.open(QIODevice::ReadOnly);
|
||||
auto testSyncJson = QJsonDocument::fromJson(testSyncFile.readAll()).object();
|
||||
auto timelineJson = testSyncJson["timeline"_L1].toObject();
|
||||
timelineJson["events"_L1] = multiplyEvents(timelineJson["events"_L1].toArray(), 100);
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
<name xml:lang="ta">நியோச்சாட்</name>
|
||||
<name xml:lang="tr">NeoChat</name>
|
||||
<name xml:lang="uk">NeoChat</name>
|
||||
<name xml:lang="x-test">xxNeoChatxx</name>
|
||||
<name xml:lang="zh-CN">NeoChat</name>
|
||||
<name xml:lang="zh-TW">NeoChat</name>
|
||||
<summary>Chat on Matrix</summary>
|
||||
@@ -82,6 +83,7 @@
|
||||
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
|
||||
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
|
||||
<summary xml:lang="uk">Спілкування у Matrix</summary>
|
||||
<summary xml:lang="x-test">xxChat on Matrixxx</summary>
|
||||
<summary xml:lang="zh-TW">在 Matrix 上聊天</summary>
|
||||
<description>
|
||||
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
|
||||
@@ -115,9 +117,10 @@
|
||||
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
|
||||
<p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.xx</p>
|
||||
<p xml:lang="zh-TW">NeoChat 是一個讓您能夠完全利用 Matrix 網路的聊天應用程式。它讓您安全地傳送文字訊息、影片或音訊檔給家人、同事或朋友等等。</p>
|
||||
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
|
||||
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. يوفر نيوتشات كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP و تعدد الخيوط وبعض جوانب التعمية من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار، ولكن يبقى الهدف توفير تطبيق للمواصفات بأكملها.</p>
|
||||
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
|
||||
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="de">NeoChat versucht eine vollumfängliche Anwendung für die Spezifikation von Matrix zu sein. Damit wird alles der aktuellen stabilen Spezifikation mit den erwähnenswerten Ausnahmen von VoIP, Diskussionsfäden und ein paar Teilen der Ende-zu-Ende-Verschlüsselung unterstützt. Zudem sind andere kleinere Auslassungen vorhanden, da sich die Matrixspezifikation ständig weiterentwickelt. Nichtsdestotrotz soll letztendlich die gesamte Spezifikation unterstützt werden.</p>
|
||||
@@ -148,9 +151,10 @@
|
||||
<p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifreleme’nin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
|
||||
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
|
||||
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
|
||||
<p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分,這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p>
|
||||
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يوفر نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
|
||||
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
|
||||
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="de">Durch die Weiterentwicklung der Matrix-Spezifikation unterstützt auch NeoChat einige als noch instabil gekennzeichnete Funktionen. Derzeit sind das:</p>
|
||||
@@ -182,6 +186,7 @@
|
||||
<p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் விதத்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
|
||||
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
|
||||
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
|
||||
<p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故,NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p>
|
||||
<ul>
|
||||
<li>Polls - MSC3381</li>
|
||||
@@ -216,6 +221,7 @@
|
||||
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
|
||||
<li xml:lang="tr">Anketler — MSC3381</li>
|
||||
<li xml:lang="uk">Опитування - MSC3381</li>
|
||||
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
|
||||
<li xml:lang="zh-TW">投票 - MSC3381</li>
|
||||
<li>Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
|
||||
@@ -249,6 +255,7 @@
|
||||
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
|
||||
<li xml:lang="tr">Çıkartma Paketleri — MSC2545</li>
|
||||
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
|
||||
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
|
||||
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
|
||||
<li>Location Events - MSC3488</li>
|
||||
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
|
||||
@@ -282,6 +289,7 @@
|
||||
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
|
||||
<li xml:lang="tr">Konum Etkinlikleri — MSC3488</li>
|
||||
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
|
||||
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
|
||||
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
|
||||
</ul>
|
||||
</description>
|
||||
@@ -298,8 +306,8 @@
|
||||
<keyword>Matrix</keyword>
|
||||
<keyword>Kirigami</keyword>
|
||||
</keywords>
|
||||
<developer id="org.kde">
|
||||
<name translate="no">KDE</name>
|
||||
<developer id="kde.org">
|
||||
<name>The KDE Community</name>
|
||||
<url>https://kde.org</url>
|
||||
</developer>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
@@ -351,6 +359,7 @@
|
||||
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
|
||||
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
|
||||
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
|
||||
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
|
||||
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
|
||||
</screenshot>
|
||||
<screenshot type="default">
|
||||
@@ -387,6 +396,7 @@
|
||||
<caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption>
|
||||
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
|
||||
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
|
||||
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
|
||||
<caption xml:lang="zh-TW">利用 Matrix 聊天空間發現新的社群</caption>
|
||||
</screenshot>
|
||||
<!--
|
||||
@@ -431,6 +441,7 @@
|
||||
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
|
||||
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
|
||||
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
|
||||
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
|
||||
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
|
||||
</screenshot>
|
||||
<screenshot environment="windows">
|
||||
@@ -469,6 +480,7 @@
|
||||
<caption xml:lang="ta">நுழைவுத் திரை</caption>
|
||||
<caption xml:lang="tr">Oturum açma ekranı</caption>
|
||||
<caption xml:lang="uk">Вікно входу</caption>
|
||||
<caption xml:lang="x-test">xxLogin screenxx</caption>
|
||||
<caption xml:lang="zh-TW">登入畫面</caption>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
@@ -476,7 +488,6 @@
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="25.08.1" date="2025-09-11"/>
|
||||
<release version="25.08.0" date="2025-08-14"/>
|
||||
<release version="25.04.3" date="2025-07-03"/>
|
||||
<release version="25.04.2" date="2025-06-05"/>
|
||||
|
||||
@@ -44,6 +44,7 @@ Name[sv]=NeoChat
|
||||
Name[ta]=நியோச்சாட்
|
||||
Name[tr]=NeoChat
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
Name[zh_TW]=NeoChat
|
||||
GenericName=Matrix Client
|
||||
@@ -87,6 +88,7 @@ GenericName[sv]=Matrix-klient
|
||||
GenericName[ta]=Matrix வாங்கி
|
||||
GenericName[tr]=Matrix İstemcisi
|
||||
GenericName[uk]=Клієнт Matrix
|
||||
GenericName[x-test]=xxMatrix Clientxx
|
||||
GenericName[zh_CN]=Matrix 客户端
|
||||
GenericName[zh_TW]=Matrix 用戶端
|
||||
Comment=Chat on Matrix
|
||||
@@ -119,6 +121,7 @@ Comment[sv]=Chatta på Matrix
|
||||
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
|
||||
Comment[tr]=Matrix üzerinde sohbet edin
|
||||
Comment[uk]=Спілкування у Matrix
|
||||
Comment[x-test]=xxChat on Matrixxx
|
||||
Comment[zh_CN]=在 Matrix 上聊天
|
||||
Comment[zh_TW]=在 Matrix 上聊天
|
||||
MimeType=x-scheme-handler/matrix;
|
||||
|
||||
2241
po/ar/neochat.po
2241
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
2012
po/ast/neochat.po
2012
po/ast/neochat.po
File diff suppressed because it is too large
Load Diff
2228
po/az/neochat.po
2228
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
2138
po/ca/neochat.po
2138
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2324
po/cs/neochat.po
2324
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
2343
po/da/neochat.po
2343
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
2405
po/de/neochat.po
2405
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
2289
po/el/neochat.po
2289
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
2387
po/en_GB/neochat.po
2387
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
2399
po/eo/neochat.po
2399
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
2063
po/es/neochat.po
2063
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
2203
po/eu/neochat.po
2203
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
2503
po/fi/neochat.po
2503
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
2194
po/fr/neochat.po
2194
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
2553
po/gl/neochat.po
2553
po/gl/neochat.po
File diff suppressed because it is too large
Load Diff
2147
po/he/neochat.po
2147
po/he/neochat.po
File diff suppressed because it is too large
Load Diff
2391
po/hi/neochat.po
2391
po/hi/neochat.po
File diff suppressed because it is too large
Load Diff
2547
po/hu/neochat.po
2547
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
2279
po/ia/neochat.po
2279
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
2300
po/id/neochat.po
2300
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
2172
po/ie/neochat.po
2172
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
2162
po/it/neochat.po
2162
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
2012
po/ja/neochat.po
2012
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
2161
po/ka/neochat.po
2161
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
2523
po/ko/neochat.po
2523
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
2013
po/lt/neochat.po
2013
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
2441
po/lv/neochat.po
2441
po/lv/neochat.po
File diff suppressed because it is too large
Load Diff
2145
po/nl/neochat.po
2145
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
2268
po/nn/neochat.po
2268
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
2216
po/pa/neochat.po
2216
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
2518
po/pl/neochat.po
2518
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
2298
po/pt/neochat.po
2298
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
2165
po/pt_BR/neochat.po
2165
po/pt_BR/neochat.po
File diff suppressed because it is too large
Load Diff
2515
po/ru/neochat.po
2515
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
2391
po/sa/neochat.po
2391
po/sa/neochat.po
File diff suppressed because it is too large
Load Diff
2235
po/sk/neochat.po
2235
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
2213
po/sl/neochat.po
2213
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
2160
po/sv/neochat.po
2160
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
2428
po/ta/neochat.po
2428
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
2135
po/tok/neochat.po
2135
po/tok/neochat.po
File diff suppressed because it is too large
Load Diff
2205
po/tr/neochat.po
2205
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
2147
po/uk/neochat.po
2147
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
2155
po/zh_CN/neochat.po
2155
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
2500
po/zh_TW/neochat.po
2500
po/zh_TW/neochat.po
File diff suppressed because it is too large
Load Diff
@@ -48,8 +48,6 @@ if(ANDROID OR WIN32)
|
||||
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
|
||||
QT_QML_SOURCE_TYPENAME GlobalMenu
|
||||
)
|
||||
else()
|
||||
set(EXTRA_IMPORTS org.kde.purpose)
|
||||
endif()
|
||||
|
||||
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
@@ -106,7 +104,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
DEPENDENCIES
|
||||
QtCore
|
||||
QtQuick
|
||||
io.github.quotient_im.libquotient
|
||||
IMPORTS
|
||||
org.kde.neochat.libneochat
|
||||
org.kde.neochat.rooms
|
||||
@@ -118,15 +115,13 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
|
||||
org.kde.neochat.devtools
|
||||
org.kde.neochat.login
|
||||
org.kde.neochat.chatbar
|
||||
org.kde.config
|
||||
org.kde.syntaxhighlighting
|
||||
${EXTRA_IMPORTS}
|
||||
)
|
||||
|
||||
if(NOT ANDROID AND NOT WIN32)
|
||||
qt_target_qml_sources(neochat QML_FILES
|
||||
qml/ShareAction.qml
|
||||
qml/GlobalMenu.qml
|
||||
qml/EditMenu.qml
|
||||
)
|
||||
else()
|
||||
qt_target_qml_sources(neochat QML_FILES
|
||||
@@ -343,7 +338,14 @@ if(TARGET KF6::DBusAddons AND NOT WIN32)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
|
||||
endif()
|
||||
|
||||
if (TARGET KF6::KIOWidgets)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KIO)
|
||||
endif()
|
||||
|
||||
if (TARGET KUnifiedPush)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KUNIFIEDPUSH)
|
||||
target_link_libraries(neochat PUBLIC KUnifiedPush)
|
||||
|
||||
if (NOT ANDROID)
|
||||
configure_file(org.kde.neochat.service.in ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service)
|
||||
install(FILES ${CMAKE_CURRENT_BINARY_DIR}/org.kde.neochat.service DESTINATION ${KDE_INSTALL_DBUSSERVICEDIR})
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "controller.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <qt6keychain/keychain.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
@@ -13,16 +14,17 @@
|
||||
|
||||
#include <signal.h>
|
||||
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/events/roommemberevent.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Quotient/settings.h>
|
||||
|
||||
#include "accountmanager.h"
|
||||
#include "enums/roomsortparameter.h"
|
||||
#include "general_logging.h"
|
||||
#include "mediasizehelper.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/messagemodel.h"
|
||||
#include "models/pushrulemodel.h"
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "models/roomtreemodel.h"
|
||||
#include "neochatconfig.h"
|
||||
@@ -38,6 +40,14 @@
|
||||
#include "trayicon_sni.h"
|
||||
#endif
|
||||
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusInterface>
|
||||
#include <QDBusMessage>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
#include <kunifiedpush/connector.h>
|
||||
#endif
|
||||
@@ -126,10 +136,8 @@ Controller::Controller(QObject *parent)
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||
#endif
|
||||
|
||||
connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
#ifndef Q_OS_ANDROID
|
||||
QObject::connect(QGuiApplication::instance(), &QCoreApplication::aboutToQuit, QGuiApplication::instance(), [this] {
|
||||
delete m_trayIcon;
|
||||
#endif
|
||||
NeoChatConfig::self()->save();
|
||||
});
|
||||
|
||||
@@ -195,15 +203,13 @@ void Controller::setAccountManager(AccountManager *manager)
|
||||
|
||||
m_accountManager = manager;
|
||||
|
||||
if (!m_accountManager) {
|
||||
return;
|
||||
if (m_accountManager) {
|
||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||
}
|
||||
|
||||
connect(m_accountManager, &AccountManager::errorOccured, this, &Controller::errorOccured);
|
||||
connect(m_accountManager, &AccountManager::accountsLoadingChanged, this, &Controller::accountsLoadingChanged);
|
||||
connect(m_accountManager, &AccountManager::connectionAdded, this, &Controller::initConnection);
|
||||
connect(m_accountManager, &AccountManager::connectionDropped, this, &Controller::teardownConnection);
|
||||
connect(m_accountManager, &AccountManager::activeConnectionChanged, this, &Controller::initActiveConnection);
|
||||
}
|
||||
|
||||
void Controller::initConnection(NeoChatConnection *connection)
|
||||
@@ -259,8 +265,8 @@ bool Controller::supportSystemTray() const
|
||||
#ifdef Q_OS_ANDROID
|
||||
return false;
|
||||
#else
|
||||
QStringList unsupportedPlatforms{u"GNOME"_s, u"Pantheon"_s};
|
||||
return !unsupportedPlatforms.contains(QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP")));
|
||||
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
|
||||
return de != u"GNOME"_s && de != u"Pantheon"_s;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -270,8 +276,11 @@ void Controller::setQuitOnLastWindowClosed()
|
||||
if (supportSystemTray() && NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
} else if (m_trayIcon) {
|
||||
delete m_trayIcon;
|
||||
} else {
|
||||
if (m_trayIcon) {
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -328,7 +337,30 @@ void Controller::clearInvitationNotification(const QString &roomId)
|
||||
|
||||
void Controller::updateBadgeNotificationCount(int count)
|
||||
{
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
// copied from Telegram desktop
|
||||
const auto launcherUrl = "application://org.kde.neochat.desktop"_L1;
|
||||
// Gnome requires that count is a 64bit integer
|
||||
const qint64 counterSlice = std::min(count, 9999);
|
||||
QVariantMap dbusUnityProperties;
|
||||
|
||||
if (counterSlice > 0) {
|
||||
dbusUnityProperties["count"_L1] = counterSlice;
|
||||
dbusUnityProperties["count-visible"_L1] = true;
|
||||
} else {
|
||||
dbusUnityProperties["count-visible"_L1] = false;
|
||||
}
|
||||
|
||||
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_L1, "com.canonical.Unity.LauncherEntry"_L1, "Update"_L1);
|
||||
|
||||
signal.setArguments({launcherUrl, dbusUnityProperties});
|
||||
|
||||
QDBusConnection::sessionBus().send(signal);
|
||||
#endif // Q_OS_ANDROID
|
||||
#else
|
||||
qGuiApp->setBadgeNumber(count);
|
||||
#endif // QT_VERSION_CHECK(6, 6, 0)
|
||||
}
|
||||
|
||||
bool Controller::isFlatpak() const
|
||||
@@ -349,10 +381,7 @@ QString Controller::loadFileContent(const QString &path) const
|
||||
{
|
||||
QUrl url(path);
|
||||
QFile file(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
if (!file.open(QFile::ReadOnly)) {
|
||||
qCWarning(GENERAL) << "Failed to open file" << path;
|
||||
return {};
|
||||
}
|
||||
file.open(QFile::ReadOnly);
|
||||
return QString::fromLatin1(file.readAll());
|
||||
}
|
||||
|
||||
|
||||
@@ -110,9 +110,8 @@ private:
|
||||
void initActiveConnection(NeoChatConnection *oldConnection, NeoChatConnection *newConnection);
|
||||
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
#ifndef Q_OS_ANDROID
|
||||
QPointer<TrayIcon> m_trayIcon;
|
||||
#endif
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
QString m_endpoint;
|
||||
QStringList m_shownImages;
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/roommember.h>
|
||||
@@ -24,7 +25,11 @@ class CommonRoomsModel : public QAbstractListModel
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
RoomIdRole = Qt::DisplayRole,
|
||||
TextRole = Qt::DisplayRole,
|
||||
LongitudeRole,
|
||||
LatitudeRole,
|
||||
AssetRole,
|
||||
AuthorRole,
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
|
||||
@@ -58,7 +58,7 @@ QVariant NotificationsModel::data(const QModelIndex &index, int role) const
|
||||
QHash<int, QByteArray> NotificationsModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{TextRole, "notificationText"},
|
||||
{TextRole, "text"},
|
||||
{RoomIdRole, "roomId"},
|
||||
{AuthorName, "authorName"},
|
||||
{AuthorAvatar, "authorAvatar"},
|
||||
|
||||
@@ -42,6 +42,7 @@ Name[sv]=NeoChat
|
||||
Name[ta]=நியோச்சாட்
|
||||
Name[tr]=NeoChat
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
Name[zh_TW]=NeoChat
|
||||
DesktopEntry=org.kde.neochat
|
||||
@@ -86,6 +87,7 @@ Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokolle
|
||||
Comment[ta]=மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி
|
||||
Comment[tr]=Merkezi olmayan iletişim protokolü Matrix için bir istemci
|
||||
Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними
|
||||
Comment[x-test]=xxA client for matrix, the decentralized communication protocolxx
|
||||
Comment[zh_CN]=分布式通讯协议 Matrix 的客户端
|
||||
Comment[zh_TW]=去中心化通訊協定 Matrix 的用戶端
|
||||
|
||||
@@ -132,6 +134,7 @@ Name[sv]=Nytt meddelande
|
||||
Name[ta]=புதிய செய்தி
|
||||
Name[tr]=Yeni İleti
|
||||
Name[uk]=Нове повідомлення
|
||||
Name[x-test]=xxNew messagexx
|
||||
Name[zh_CN]=新消息
|
||||
Name[zh_TW]=新訊息
|
||||
Comment=There is a new message
|
||||
@@ -174,6 +177,7 @@ Comment[sv]=Det finns ett nytt meddelande
|
||||
Comment[ta]=ஒரு புதிய செய்தி உள்ளது
|
||||
Comment[tr]=Yeni bir ileti var
|
||||
Comment[uk]=Надійшло нове повідомлення
|
||||
Comment[x-test]=xxThere is a new messagexx
|
||||
Comment[zh_CN]=有新消息
|
||||
Comment[zh_TW]=有新的訊息
|
||||
Action=Popup
|
||||
@@ -218,6 +222,7 @@ Name[sv]=Ny inbjudan
|
||||
Name[ta]=புதிய அழைப்பிதழ்
|
||||
Name[tr]=Yeni Davet
|
||||
Name[uk]=Нове запрошення
|
||||
Name[x-test]=xxNew Invitationxx
|
||||
Name[zh_CN]=新邀请
|
||||
Name[zh_TW]=新邀請
|
||||
Comment=There is a new invitation to a room
|
||||
@@ -259,6 +264,7 @@ Comment[sv]=Det finns en ny inbjudan till ett rum
|
||||
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
|
||||
Comment[tr]=Bir odaya yeni bir davetiye var
|
||||
Comment[uk]=У кімнаті нове запрошення
|
||||
Comment[x-test]=xxThere is a new invitation to a roomxx
|
||||
Comment[zh_CN]=有新的聊天室邀请
|
||||
Comment[zh_TW]=有新的加入聊天室邀請
|
||||
Action=Popup
|
||||
@@ -297,6 +303,7 @@ Name[sv]=Dela
|
||||
Name[ta]=பகிர்
|
||||
Name[tr]=Paylaş
|
||||
Name[uk]=Оприлюднення
|
||||
Name[x-test]=xxSharexx
|
||||
Name[zh_CN]=分享
|
||||
Name[zh_TW]=分享
|
||||
Comment=The result of sharing a piece of content
|
||||
@@ -331,6 +338,7 @@ Comment[sv]=Resultatet av att dela innehåll
|
||||
Comment[ta]=எதையோ பகிர்ந்ததன் விளைவு
|
||||
Comment[tr]=Bir parça içerik paylaşımının sonucu
|
||||
Comment[uk]=Результат оприлюднення даних
|
||||
Comment[x-test]=xxThe result of sharing a piece of contentxx
|
||||
Comment[zh_CN]=分享一个内容得到的结果
|
||||
Comment[zh_TW]=分享一份內容之後的結果
|
||||
Action=Popup
|
||||
|
||||
@@ -189,10 +189,6 @@
|
||||
<label>Don't hide any events in the timeline</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="RelateAnyEvent" type="bool">
|
||||
<label>Send relations to any event, including state events and events normally hidden.</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="AlwaysVerifyDevice" type="bool">
|
||||
<label>Always allow device verification</label>
|
||||
<default>false</default>
|
||||
|
||||
@@ -60,8 +60,9 @@ void NotificationsManager::startNotificationJob(QPointer<NeoChatConnection> conn
|
||||
}
|
||||
|
||||
if (!m_connActiveJob.contains(connection->user()->id())) {
|
||||
auto job = connection->callApi<GetNotificationsJob>();
|
||||
m_connActiveJob.append(connection->user()->id());
|
||||
connection->callApi<GetNotificationsJob>().onResult([this, connection](const auto &job) {
|
||||
connect(job, &BaseJob::success, this, [this, job, connection]() {
|
||||
m_connActiveJob.removeAll(connection->user()->id());
|
||||
processNotificationJob(connection, job, !m_oldNotifications.contains(connection->user()->id()));
|
||||
});
|
||||
@@ -432,7 +433,7 @@ QPixmap NotificationsManager::createNotificationImage(const QImage &icon, NeoCha
|
||||
|
||||
if (room != nullptr) {
|
||||
const QImage roomAvatar = room->avatar(imageRect.width(), imageRect.height());
|
||||
if (!roomAvatar.isNull() && icon != roomAvatar) {
|
||||
if (icon != roomAvatar) {
|
||||
const QRect lowerQuarter{imageRect.center(), imageRect.size() / 2};
|
||||
|
||||
painter.setBrush(Qt::white);
|
||||
|
||||
@@ -43,6 +43,7 @@ Name[sv]=NeoChat
|
||||
Name[ta]=நியோச்சாட்
|
||||
Name[tr]=NeoChat
|
||||
Name[uk]=NeoChat
|
||||
Name[x-test]=xxNeoChatxx
|
||||
Name[zh_CN]=NeoChat
|
||||
Name[zh_TW]=NeoChat
|
||||
Comment=Find rooms in NeoChat
|
||||
@@ -82,6 +83,7 @@ Comment[sv]=Sök efter rum i NeoChat
|
||||
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
|
||||
Comment[tr]=NeoChat’te odalar bulun
|
||||
Comment[uk]=Пошук кімнат у NeoChat
|
||||
Comment[x-test]=xxFind rooms in NeoChatxx
|
||||
Comment[zh_CN]=在 NeoChat 查找聊天室
|
||||
Comment[zh_TW]=在 NeoChat 尋找聊天室
|
||||
X-KDE-ServiceTypes=Plasma/Runner
|
||||
@@ -91,3 +93,4 @@ X-Plasma-API=DBus
|
||||
X-Plasma-DBusRunner-Service=org.kde.neochat
|
||||
X-Plasma-DBusRunner-Path=/RoomRunner
|
||||
X-Plasma-Request-Actions-Once=true
|
||||
X-Plasma-Runner-Min-Letter-Count=3
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
// SPDX-FileCopyrightText: 2022 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 ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.settings
|
||||
import org.kde.neochat.devtools
|
||||
|
||||
KirigamiComponents.ConvergentContextMenu {
|
||||
id: root
|
||||
@@ -18,17 +18,21 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
required property NeoChatConnection connection
|
||||
required property Kirigami.ApplicationWindow window
|
||||
|
||||
Kirigami.Action {
|
||||
QQC2.Action {
|
||||
text: i18nc("@action:button", "Show QR Code")
|
||||
icon.name: "view-barcode-qr-symbolic"
|
||||
onTriggered: {
|
||||
(Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||
let qrMax = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
|
||||
text: "https://matrix.to/#/" + root.connection.localUser.id,
|
||||
title: root.connection.localUser.displayName,
|
||||
subtitle: root.connection.localUser.id,
|
||||
// Note: User::avatarUrl does not set user_id, and thus cannot be used directly here. Hence the makeMediaUrl.
|
||||
avatarSource: root.connection.localUser.avatarUrl.toString().length > 0 ? root.connection.makeMediaUrl(root.connection.localUser.avatarUrl) : ""
|
||||
}) as QrCodeMaximizeComponent).open();
|
||||
});
|
||||
if (typeof root.closeDialog === "function") {
|
||||
root.closeDialog();
|
||||
}
|
||||
qrMax.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,27 +40,26 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
text: i18nc("@action:inmenu", "Switch Account")
|
||||
icon.name: "system-switch-user"
|
||||
shortcut: "Ctrl+U"
|
||||
onTriggered: (Qt.createComponent("org.kde.neochat", "AccountSwitchDialog").createObject(QQC2.Overlay.overlay, {
|
||||
onTriggered: accountSwitchDialog.createObject(QQC2.Overlay.overlay, {
|
||||
connection: root.connection
|
||||
}) as Kirigami.Dialog).open();
|
||||
}).open();
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Edit This Account")
|
||||
QQC2.Action {
|
||||
text: i18n("Edit This Account")
|
||||
icon.name: "document-edit"
|
||||
onTriggered: NeoChatSettingsView.openWithInitialProperties("accounts", {initialAccount: root.connection});
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Notification Settings")
|
||||
QQC2.Action {
|
||||
text: i18n("Notification Settings")
|
||||
icon.name: "notifications"
|
||||
onTriggered: {
|
||||
NeoChatSettingsView.open('notifications');
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Devices")
|
||||
QQC2.Action {
|
||||
text: i18n("Devices")
|
||||
icon.name: "computer-symbolic"
|
||||
onTriggered: {
|
||||
NeoChatSettingsView.open('devices');
|
||||
@@ -64,10 +67,10 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Open Developer Tools")
|
||||
text: i18n("Open Developer Tools")
|
||||
icon.name: "tools"
|
||||
visible: NeoChatConfig.developerTools
|
||||
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'DevtoolsPage'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title:window", "Developer Tools"),
|
||||
@@ -85,10 +88,9 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
})
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
QQC2.Action {
|
||||
text: i18nc("@action:inmenu", "Verify This Device")
|
||||
icon.name: "security-low"
|
||||
visible: !root.connection.isVerifiedSession
|
||||
onTriggered: {
|
||||
root.connection.startSelfVerification();
|
||||
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
|
||||
@@ -97,17 +99,19 @@ KirigamiComponents.ConvergentContextMenu {
|
||||
standardButtons: Kirigami.Dialog.Ok
|
||||
})
|
||||
dialog.open();
|
||||
root.connection.newKeyVerificationSession.connect(() => {
|
||||
root.connection.onNewKeyVerificationSession.connect(() => {
|
||||
dialog.close();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Action {
|
||||
text: i18nc("@action:inmenu", "Logout…")
|
||||
QQC2.Action {
|
||||
text: i18n("Logout")
|
||||
icon.name: "im-kick-user"
|
||||
onTriggered: (Qt.createComponent("org.kde.neochat", "ConfirmLogoutDialog").createObject(QQC2.Overlay.overlay, {
|
||||
connection: root.connection
|
||||
}) as Kirigami.Dialog).open()
|
||||
onTriggered: confirmLogoutDialogComponent.createObject(root).open()
|
||||
}
|
||||
|
||||
readonly property Component confirmLogoutDialogComponent: ConfirmLogoutDialog {
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,7 @@
|
||||
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
@@ -19,6 +16,8 @@ Kirigami.Dialog {
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
leftPadding: 0
|
||||
rightPadding: 0
|
||||
topPadding: 0
|
||||
@@ -26,7 +25,7 @@ Kirigami.Dialog {
|
||||
|
||||
standardButtons: Kirigami.Dialog.NoButton
|
||||
|
||||
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
|
||||
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||
title: i18nc("@title: dialog to switch between logged in accounts", "Switch Account")
|
||||
|
||||
onVisibleChanged: if (visible) {
|
||||
@@ -54,14 +53,14 @@ Kirigami.Dialog {
|
||||
}
|
||||
text: i18nc("@button: login to or register a new account.", "Add Account")
|
||||
contentItem: Delegates.SubtitleContentItem {
|
||||
itemDelegate: addDelegate
|
||||
subtitle: i18nc("@info", "Log in or create a new account")
|
||||
itemDelegate: parent
|
||||
subtitle: i18n("Log in or create a new account")
|
||||
labelItem.textFormat: Text.PlainText
|
||||
subtitleItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {}, {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {}, {
|
||||
title: i18nc("@title:window", "Login")
|
||||
});
|
||||
root.close();
|
||||
@@ -95,8 +94,8 @@ Kirigami.Dialog {
|
||||
accountView.decrementCurrentIndex();
|
||||
}
|
||||
}
|
||||
Keys.onEnterPressed: ((accountView.currentItem ?? accountView.footerItem) as Delegates.RoundedItemDelegate).clicked()
|
||||
Keys.onReturnPressed: ((accountView.currentItem ?? accountView.footerItem) as Delegates.RoundedItemDelegate).clicked()
|
||||
Keys.onEnterPressed: accountView.currentItem.clicked()
|
||||
Keys.onReturnPressed: accountView.currentItem.clicked()
|
||||
|
||||
onVisibleChanged: {
|
||||
for (let i = 0; i < accountView.count; i++) {
|
||||
|
||||
@@ -6,6 +6,8 @@ import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Dialog {
|
||||
id: root
|
||||
|
||||
@@ -18,7 +20,7 @@ Kirigami.Dialog {
|
||||
title: i18nc("@title:dialog", "Start a chat")
|
||||
|
||||
contentItem: QQC2.Label {
|
||||
text: i18nc("@info", "Do you want to start a chat with %1?", root.user.displayName)
|
||||
text: i18n("Do you want to start a chat with %1?", root.user.displayName)
|
||||
textFormat: Text.PlainText
|
||||
wrapMode: Text.Wrap
|
||||
horizontalAlignment: Qt.AlignHCenter
|
||||
|
||||
@@ -33,7 +33,7 @@ ColumnLayout {
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
id: editImageButton
|
||||
visible: root.hasImage
|
||||
visible: hasImage
|
||||
icon.name: "document-edit"
|
||||
text: i18n("Edit")
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
@@ -46,9 +46,9 @@ ColumnLayout {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
let imageEditor = (Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(imageEditorPage);
|
||||
let imageEditor = applicationWindow().pageStack.pushDialogLayer(imageEditorPage);
|
||||
imageEditor.newPathChanged.connect(function (newPath) {
|
||||
imageEditor.closeDialog();
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
root.attachmentPath = newPath;
|
||||
});
|
||||
}
|
||||
@@ -58,18 +58,14 @@ ColumnLayout {
|
||||
QQC2.ToolButton {
|
||||
id: cancelAttachmentButton
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Cancel sending attachment")
|
||||
icon.name: "dialog-close"
|
||||
onClicked: root.attachmentCancelled()
|
||||
|
||||
action: Kirigami.Action {
|
||||
text: i18n("Cancel sending attachment")
|
||||
icon.name: "dialog-close"
|
||||
onTriggered: attachmentCancelled()
|
||||
shortcut: "Escape"
|
||||
}
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
|
||||
Kirigami.Action {
|
||||
shortcut: "Escape"
|
||||
onTriggered: cancelAttachmentButton.clicked()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +75,8 @@ ColumnLayout {
|
||||
|
||||
asynchronous: true
|
||||
cache: false // Cache is not needed. Images will rarely be shown repeatedly.
|
||||
source: root.hasImage ? root.attachmentPath : ""
|
||||
visible: root.hasImage
|
||||
source: hasImage ? root.attachmentPath : ""
|
||||
visible: hasImage
|
||||
fillMode: Image.PreserveAspectFit
|
||||
|
||||
onSourceChanged: {
|
||||
@@ -118,11 +114,11 @@ ColumnLayout {
|
||||
id: mimetypeIcon
|
||||
implicitWidth: Kirigami.Units.iconSizes.smallMedium
|
||||
implicitHeight: Kirigami.Units.iconSizes.smallMedium
|
||||
source: root.attachmentMimetype.iconName
|
||||
source: attachmentMimetype.iconName
|
||||
}
|
||||
QQC2.Label {
|
||||
id: fileLabel
|
||||
text: root.baseFileName
|
||||
text: baseFileName
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Templates as T
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: root
|
||||
|
||||
@@ -48,7 +48,7 @@ Components.AbstractMaximizeComponent {
|
||||
implicitWidth: Kirigami.Units.iconSizes.medium
|
||||
implicitHeight: Kirigami.Units.iconSizes.medium
|
||||
|
||||
name: root.author.displayName
|
||||
name: root.author.name ?? root.author.displayName
|
||||
source: root.author.avatarUrl
|
||||
color: root.author.color
|
||||
}
|
||||
@@ -57,7 +57,7 @@ Components.AbstractMaximizeComponent {
|
||||
QQC2.Label {
|
||||
id: userLabel
|
||||
|
||||
text: root.author.displayName
|
||||
text: root.author.name ?? root.author.displayName
|
||||
color: root.author.color
|
||||
font.weight: Font.Bold
|
||||
elide: Text.ElideRight
|
||||
|
||||
@@ -8,6 +8,7 @@ import QtQml.Models
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
import org.kde.kitemmodels
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
|
||||
86
src/app/qml/EditMenu.qml
Normal file
86
src/app/qml/EditMenu.qml
Normal file
@@ -0,0 +1,86 @@
|
||||
// SPDX-FileCopyrightText: 2021 Carson Black <uhhadd@gmail.com>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import Qt.labs.platform as Labs
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
|
||||
Labs.Menu {
|
||||
id: root
|
||||
|
||||
required property Item field
|
||||
|
||||
Labs.MenuItem {
|
||||
enabled: root.field !== null && root.field.canUndo
|
||||
text: i18nc("text editing menu action", "Undo")
|
||||
shortcut: StandardKey.Undo
|
||||
onTriggered: {
|
||||
root.field.undo();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Labs.MenuItem {
|
||||
enabled: root.field !== null && root.field.canRedo
|
||||
text: i18nc("text editing menu action", "Redo")
|
||||
shortcut: StandardKey.Redo
|
||||
onTriggered: {
|
||||
root.field.undo();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Labs.MenuSeparator {}
|
||||
|
||||
Labs.MenuItem {
|
||||
enabled: root.field !== null && root.field.selectedText
|
||||
text: i18nc("text editing menu action", "Cut")
|
||||
shortcut: StandardKey.Cut
|
||||
onTriggered: {
|
||||
root.field.cut();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Labs.MenuItem {
|
||||
enabled: root.field !== null && root.field.selectedText
|
||||
text: i18nc("text editing menu action", "Copy")
|
||||
shortcut: StandardKey.Copy
|
||||
onTriggered: {
|
||||
root.field.copy();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Labs.MenuItem {
|
||||
enabled: root.field !== null && root.field.canPaste
|
||||
text: i18nc("text editing menu action", "Paste")
|
||||
shortcut: StandardKey.Paste
|
||||
onTriggered: {
|
||||
root.field.paste();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Labs.MenuItem {
|
||||
enabled: root.field !== null && root.field.selectedText !== ""
|
||||
text: i18nc("text editing menu action", "Delete")
|
||||
shortcut: ""
|
||||
onTriggered: {
|
||||
root.field.remove(root.field.selectionStart, root.field.selectionEnd);
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
Labs.MenuSeparator {}
|
||||
|
||||
Labs.MenuItem {
|
||||
enabled: root.field !== null
|
||||
text: i18nc("text editing menu action", "Select All")
|
||||
shortcut: StandardKey.SelectAll
|
||||
onTriggered: {
|
||||
root.field.selectAll();
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,15 +6,16 @@ import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.neochat
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
required property string emoji
|
||||
required property string description
|
||||
property alias emoji: emojiLabel.text
|
||||
property alias description: descriptionLabel.text
|
||||
|
||||
QQC2.Label {
|
||||
text: root.emoji
|
||||
id: emojiLabel
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
|
||||
@@ -24,7 +25,7 @@ ColumnLayout {
|
||||
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4
|
||||
}
|
||||
QQC2.Label {
|
||||
text: root.description
|
||||
id: descriptionLabel
|
||||
Layout.fillWidth: true
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
horizontalAlignment: Text.AlignHCenter
|
||||
|
||||
@@ -17,6 +17,9 @@ RowLayout {
|
||||
|
||||
Repeater {
|
||||
id: repeater
|
||||
delegate: EmojiItem {}
|
||||
delegate: EmojiItem {
|
||||
emoji: modelData.emoji
|
||||
description: modelData.description
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ ApplicationWindow {
|
||||
property real longitude: NaN
|
||||
property string asset
|
||||
property var author
|
||||
property LiveLocationsModel liveLocationModel: null
|
||||
property QtObject liveLocationModel: null
|
||||
|
||||
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
||||
visibility: Qt.WindowFullScreen
|
||||
@@ -59,7 +59,7 @@ ApplicationWindow {
|
||||
|
||||
Connections {
|
||||
target: mapView.map
|
||||
function onCopyrightLinkActivated(link: string) {
|
||||
function onCopyrightLinkActivated() {
|
||||
Qt.openUrlExternally(link);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import Qt.labs.platform as Labs
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Window
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
@@ -15,50 +16,12 @@ Labs.MenuBar {
|
||||
id: root
|
||||
|
||||
required property NeoChatConnection connection
|
||||
required property Kirigami.ApplicationWindow appWindow
|
||||
|
||||
Labs.Menu {
|
||||
title: i18nc("menu", "File")
|
||||
title: i18nc("menu", "NeoChat")
|
||||
|
||||
Labs.MenuItem {
|
||||
icon.name: "list-add-user"
|
||||
text: i18nc("@action:inmenu", "Find your Friends")
|
||||
enabled: root.connection
|
||||
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Find your friends")
|
||||
})
|
||||
}
|
||||
Labs.MenuItem {
|
||||
icon.name: "system-users-symbolic"
|
||||
text: i18nc("@action:inmenu", "Create a Room…")
|
||||
enabled: root.connection
|
||||
shortcut: StandardKey.New
|
||||
onTriggered: {
|
||||
Qt.createComponent('org.kde.neochat', 'CreateRoomDialog').createObject(root.appWindow, {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Create a Room")
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
Labs.MenuItem {
|
||||
icon.name: "compass-symbolic"
|
||||
text: i18nc("@action:inmenu", "Explore Rooms")
|
||||
enabled: root.connection
|
||||
onTriggered: {
|
||||
let dialog = root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Explore Rooms")
|
||||
});
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
||||
});
|
||||
}
|
||||
}
|
||||
Labs.MenuItem {
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Configure NeoChat…")
|
||||
text: i18nc("menu", "Configure NeoChat…")
|
||||
|
||||
shortcut: StandardKey.Preferences
|
||||
@@ -71,15 +34,58 @@ Labs.MenuBar {
|
||||
onTriggered: Qt.quit()
|
||||
}
|
||||
}
|
||||
Labs.Menu {
|
||||
title: i18nc("menu", "File")
|
||||
|
||||
Labs.MenuItem {
|
||||
icon.name: "list-add-user"
|
||||
text: i18nc("@action:inmenu", "Find your Friends")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Find your friends")
|
||||
})
|
||||
}
|
||||
Labs.MenuItem {
|
||||
icon.name: "system-users-symbolic"
|
||||
text: i18nc("@action:inmenu", "Create a Room…")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
|
||||
shortcut: StandardKey.New
|
||||
onTriggered: {
|
||||
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'CreateRoomDialog'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Create a Room")
|
||||
});
|
||||
}
|
||||
}
|
||||
Labs.MenuItem {
|
||||
icon.name: "compass-symbolic"
|
||||
text: i18nc("@action:inmenu", "Explore Rooms")
|
||||
onTriggered: {
|
||||
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Explore Rooms")
|
||||
});
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
EditMenu {
|
||||
title: i18nc("menu", "Edit")
|
||||
field: (root.activeFocusItem instanceof TextEdit || root.activeFocusItem instanceof TextInput) ? root.activeFocusItem : null
|
||||
}
|
||||
Labs.Menu {
|
||||
title: i18nc("menu", "View")
|
||||
|
||||
Labs.MenuItem {
|
||||
icon.name: "search-symbolic"
|
||||
enabled: root.connection
|
||||
text: i18nc("@action:inmenu opens a UI element called the 'Quick Switcher', which offers a fast keyboard-based interface for switching in between chats.", "Search Rooms")
|
||||
onTriggered: (root.appWindow as Main).quickSwitcher.open()
|
||||
onTriggered: quickSwitcher.open()
|
||||
}
|
||||
}
|
||||
Labs.Menu {
|
||||
@@ -87,8 +93,8 @@ Labs.MenuBar {
|
||||
|
||||
Labs.MenuItem {
|
||||
icon.name: "view-fullscreen-symbolic"
|
||||
text: root.appWindow.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
|
||||
onTriggered: root.appWindow.visibility === Window.FullScreen ? root.appWindow.showNormal() : root.appWindow.showFullScreen()
|
||||
text: root.visibility === Window.FullScreen ? i18nc("menu", "Exit Full Screen") : i18nc("menu", "Enter Full Screen")
|
||||
onTriggered: root.visibility === Window.FullScreen ? root.showNormal() : root.showFullScreen()
|
||||
}
|
||||
}
|
||||
Labs.Menu {
|
||||
@@ -97,12 +103,12 @@ Labs.MenuBar {
|
||||
Labs.MenuItem {
|
||||
icon.name: "help-about-symbolic"
|
||||
text: i18nc("menu", "About NeoChat")
|
||||
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutPage"))
|
||||
}
|
||||
Labs.MenuItem {
|
||||
icon.name: "kde-symbolic"
|
||||
text: i18nc("menu", "About KDE")
|
||||
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.kirigamiaddons.formcard", "AboutKDEPage"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,10 +3,8 @@
|
||||
|
||||
import QtQuick
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.neochat
|
||||
|
||||
Item {
|
||||
required property NeoChatConnection connection
|
||||
required property Kirigami.ApplicationWindow appWindow
|
||||
}
|
||||
|
||||
@@ -3,54 +3,29 @@
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
RowLayout {
|
||||
QQC2.Control {
|
||||
id: root
|
||||
|
||||
property string text
|
||||
|
||||
onTextChanged: {
|
||||
// This is done so the text doesn't disappear for a split second while in the opacity transition
|
||||
if (root.text.length > 0) {
|
||||
urlLabel.text = root.text
|
||||
}
|
||||
visible: !root.text.startsWith("https://matrix.to/") && root.text.length > 0
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
z: 20
|
||||
|
||||
Accessible.ignored: true
|
||||
|
||||
contentItem: QQC2.Label {
|
||||
text: root.text.startsWith("https://matrix.to/") ? "" : root.text
|
||||
elide: Text.ElideRight
|
||||
Accessible.description: i18nc("@info screenreader", "The currently selected link")
|
||||
}
|
||||
|
||||
z: 99
|
||||
spacing: 0
|
||||
|
||||
opacity: (!root.text.startsWith("https://matrix.to/") && root.text.length > 0) ? 1 : 0
|
||||
visible: opacity > 0
|
||||
|
||||
Behavior on opacity {
|
||||
OpacityAnimator {
|
||||
duration: Kirigami.Units.shortDuration
|
||||
easing.type: Easing.InOutQuad
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Control {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||
|
||||
Accessible.ignored: true
|
||||
|
||||
contentItem: QQC2.Label {
|
||||
id: urlLabel
|
||||
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
|
||||
background: Kirigami.ShadowedRectangle {
|
||||
corners.topRightRadius: Kirigami.Units.cornerRadius
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
border {
|
||||
color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor, Kirigami.Theme.textColor, Kirigami.Theme.frameContrast)
|
||||
width: 1
|
||||
}
|
||||
}
|
||||
background: Rectangle {
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,8 +2,6 @@
|
||||
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
@@ -54,15 +52,6 @@ ColumnLayout {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
Kirigami.SelectableLabel {
|
||||
Layout.fillWidth: true
|
||||
font: Kirigami.Theme.smallFont
|
||||
textFormat: TextEdit.PlainText
|
||||
visible: root.currentRoom && root.currentRoom.canonicalAlias
|
||||
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
}
|
||||
|
||||
Kirigami.Heading {
|
||||
text: root.currentRoom.displayName
|
||||
|
||||
@@ -81,14 +70,7 @@ ColumnLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Kirigami.Heading {
|
||||
text: root.invitingMember.displayName
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
|
||||
QQC2.Label {
|
||||
text: root.invitingMember.id
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
text: root.currentRoom.displayName
|
||||
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
}
|
||||
@@ -177,7 +159,7 @@ ColumnLayout {
|
||||
|
||||
QQC2.Label {
|
||||
color: Kirigami.Theme.disabledTextColor
|
||||
text: xi18nc("@info:label Ensure you are referring to the same translation used for that settings page", "You can reject invitations from unknown users under the <interface>Security & Safety</interface> settings.")
|
||||
text: i18nc("@info:label", "You can reject invitations from unknown users under Security settings.")
|
||||
wrapMode: Text.WordWrap
|
||||
|
||||
// + 5 to prevent it from wrapping unnecessarily
|
||||
|
||||
@@ -8,6 +8,7 @@ import QtQuick.Layouts
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.components as KirigamiComponents
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
import org.kde.prison
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
@@ -24,7 +25,7 @@ Kirigami.Dialog {
|
||||
|
||||
standardButtons: Kirigami.Dialog.NoButton
|
||||
|
||||
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
|
||||
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||
title: i18nc("@title:dialog", "Join Room")
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
@@ -66,7 +67,7 @@ Kirigami.Dialog {
|
||||
text: i18nc("@action:button", "Join room")
|
||||
icon.name: "irc-join-channel"
|
||||
onClicked: {
|
||||
RoomManager.resolveResource(root.room, "join_confirmed");
|
||||
RoomManager.resolveResource(root.room, "join");
|
||||
root.close();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,14 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Window
|
||||
import QtQml
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
import org.kde.neochat
|
||||
import io.github.quotient_im.libquotient
|
||||
|
||||
Kirigami.Page {
|
||||
id: root
|
||||
@@ -25,63 +22,56 @@ Kirigami.Page {
|
||||
name: "cancelled"
|
||||
when: root.session.state === KeyVerificationSession.CANCELED
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: verificationCanceled
|
||||
target: stateLoader
|
||||
sourceComponent: verificationCanceled
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForVerification"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: emojiSas
|
||||
target: stateLoader
|
||||
sourceComponent: emojiSas
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForReady"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORREADY
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "incoming"
|
||||
when: root.session.state === KeyVerificationSession.INCOMING
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForKey"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForAccept"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "waitingForMac"
|
||||
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "ready"
|
||||
when: root.session.state === KeyVerificationSession.READY
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: chooseVerificationComponent
|
||||
target: stateLoader
|
||||
sourceComponent: chooseVerificationComponent
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "done"
|
||||
when: root.session.state === KeyVerificationSession.DONE
|
||||
PropertyChanges {
|
||||
stateLoader.sourceComponent: message
|
||||
target: stateLoader
|
||||
sourceComponent: message
|
||||
}
|
||||
}
|
||||
]
|
||||
@@ -137,9 +127,7 @@ Kirigami.Page {
|
||||
case KeyVerificationSession.WAITINGFORREADY:
|
||||
case KeyVerificationSession.INCOMING:
|
||||
case KeyVerificationSession.WAITINGFORMAC:
|
||||
case KeyVerificationSession.WAITINGFORKEY:
|
||||
case KeyVerificationSession.WAITINGFORACCEPT:
|
||||
return "security-medium-symbolic";
|
||||
return "security-medium-symbolic";
|
||||
case KeyVerificationSession.DONE:
|
||||
return "security-high";
|
||||
default:
|
||||
@@ -153,19 +141,13 @@ Kirigami.Page {
|
||||
case KeyVerificationSession.INCOMING:
|
||||
return i18n("Incoming key verification request from device **%1**", root.session.remoteDeviceId);
|
||||
case KeyVerificationSession.WAITINGFORMAC:
|
||||
return i18n("Waiting for other party to send us keys.");
|
||||
case KeyVerificationSession.WAITINGFORKEY:
|
||||
return i18n("Waiting for other party to confirm our keys.");
|
||||
case KeyVerificationSession.WAITINGFORACCEPT:
|
||||
return i18n("Waiting for other party to verify.");
|
||||
case KeyVerificationSession.DONE:
|
||||
return i18n("Successfully verified device **%1**", root.session.remoteDeviceId);
|
||||
return i18n("Successfully verified device **%1**", root.session.remoteDeviceId)
|
||||
default:
|
||||
return "";
|
||||
}
|
||||
}
|
||||
isDone: root.session.state === KeyVerificationSession.DONE
|
||||
onDone: root.Kirigami.PageStack.closeDialog()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.config as KConfig
|
||||
@@ -19,11 +20,6 @@ Kirigami.ApplicationWindow {
|
||||
|
||||
property bool initialized: false
|
||||
|
||||
readonly property QuickSwitcher quickSwitcher: QuickSwitcher {
|
||||
connection: root.connection
|
||||
window: root
|
||||
}
|
||||
|
||||
title: {
|
||||
if (NeoChatConfig.windowTitleFocus) {
|
||||
return activeFocusItem + " " + (activeFocusItem ? activeFocusItem.Accessible.name : "");
|
||||
@@ -87,7 +83,6 @@ Kirigami.ApplicationWindow {
|
||||
active: Kirigami.Settings.hasPlatformMenuBar && !Kirigami.Settings.isMobile
|
||||
sourceComponent: GlobalMenu {
|
||||
connection: root.connection
|
||||
appWindow: root
|
||||
}
|
||||
}
|
||||
|
||||
@@ -95,12 +90,19 @@ Kirigami.ApplicationWindow {
|
||||
configGroupName: "MainWindow"
|
||||
}
|
||||
|
||||
QuickSwitcher {
|
||||
id: quickSwitcher
|
||||
connection: root.connection
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: RoomManager
|
||||
|
||||
function onCurrentRoomChanged() {
|
||||
if (RoomManager.currentRoom && root.pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
|
||||
let roomPage = root.pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
|
||||
if (RoomManager.currentRoom && pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
|
||||
let roomPage = pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
|
||||
connection: root.connection
|
||||
});
|
||||
roomPage.backRequested.connect(event => {
|
||||
RoomManager.clearCurrentRoom();
|
||||
});
|
||||
@@ -108,26 +110,33 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
|
||||
function onAskJoinRoom(room) {
|
||||
(Qt.createComponent("org.kde.neochat", "JoinRoomDialog").createObject(root, {
|
||||
Qt.createComponent("org.kde.neochat", "JoinRoomDialog").createObject(root, {
|
||||
room: room,
|
||||
connection: root.connection
|
||||
}) as JoinRoomDialog).open();
|
||||
}).open();
|
||||
}
|
||||
|
||||
function onShowUserDetail(user, room) {
|
||||
root.showUserDetail(user, room);
|
||||
}
|
||||
|
||||
function goToEvent(event) {
|
||||
if (event.length > 0) {
|
||||
roomItem.goToEvent(event);
|
||||
}
|
||||
roomItem.forceActiveFocus();
|
||||
}
|
||||
|
||||
function onAskDirectChatConfirmation(user) {
|
||||
(Qt.createComponent("org.kde.neochat", "AskDirectChatConfirmation").createObject(this, {
|
||||
Qt.createComponent("org.kde.neochat", "AskDirectChatConfirmation").createObject(this, {
|
||||
user: user
|
||||
}) as AskDirectChatConfirmation).open();
|
||||
}).open();
|
||||
}
|
||||
|
||||
function onExternalUrl(url) {
|
||||
(Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(this, {
|
||||
link: url
|
||||
}) as ConfirmUrlDialog).open();
|
||||
let dialog = Qt.createComponent("org.kde.neochat", "ConfirmUrlDialog").createObject(this);
|
||||
dialog.link = url;
|
||||
dialog.open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +200,7 @@ Kirigami.ApplicationWindow {
|
||||
dim = false;
|
||||
}
|
||||
}
|
||||
enabled: RoomManager.hasOpenRoom && root.pageStack.layers.depth < 2 && root.pageStack.depth < 3 && (root.pageStack.visibleItems.length > 1 || root.pageStack.currentIndex > 0) && !Kirigami.Settings.isMobile && root.pageStack.wideMode
|
||||
enabled: RoomManager.hasOpenRoom && pageStack.layers.depth < 2 && pageStack.depth < 3 && (pageStack.visibleItems.length > 1 || pageStack.currentIndex > 0) && !Kirigami.Settings.isMobile && root.pageStack.wideMode
|
||||
handleVisible: enabled
|
||||
}
|
||||
|
||||
@@ -213,10 +222,10 @@ Kirigami.ApplicationWindow {
|
||||
Connections {
|
||||
target: NeoChatConfig
|
||||
function onBlurChanged() {
|
||||
WindowController.setBlur(root.pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
|
||||
WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
|
||||
}
|
||||
function onCompactLayoutChanged() {
|
||||
WindowController.setBlur(root.pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
|
||||
WindowController.setBlur(pageStack, NeoChatConfig.blur && !NeoChatConfig.compactLayout);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,7 +242,7 @@ Kirigami.ApplicationWindow {
|
||||
RoomListPage {
|
||||
id: roomList
|
||||
|
||||
onSearch: root.quickSwitcher.open()
|
||||
onSearch: quickSwitcher.open()
|
||||
|
||||
connection: root.connection
|
||||
|
||||
@@ -267,23 +276,12 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.connection
|
||||
function onLoggedOut(): void {
|
||||
root.pageStack.clear();
|
||||
let page = root.pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {
|
||||
showExisting: true,
|
||||
}) as WelcomePage;
|
||||
page.connectionChosen.connect(() => root.load())
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: AccountRegistry
|
||||
function onRowsRemoved() {
|
||||
if (AccountRegistry.rowCount() === 0) {
|
||||
root.pageStack.clear();
|
||||
root.pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'));
|
||||
pageStack.clear();
|
||||
pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -292,7 +290,7 @@ Kirigami.ApplicationWindow {
|
||||
target: Controller
|
||||
|
||||
function onErrorOccured(error) {
|
||||
root.showPassiveNotification(error, "short");
|
||||
showPassiveNotification(error, "short");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,9 +305,9 @@ Kirigami.ApplicationWindow {
|
||||
});
|
||||
}
|
||||
function onUserConsentRequired(url) {
|
||||
(Qt.createComponent("org.kde.neochat", "ConsentDialog").createObject(this, {
|
||||
Qt.createComponent("org.kde.neochat", "ConsentDialog").createObject(this, {
|
||||
url: url
|
||||
}) as ConsentDialog).open();
|
||||
}).open();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +338,7 @@ Kirigami.ApplicationWindow {
|
||||
}
|
||||
}
|
||||
function handleShare(): void {
|
||||
const dialog = root.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
||||
const dialog = applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ChooseRoomDialog'), {
|
||||
connection: root.connection
|
||||
}, {
|
||||
title: i18nc("@title", "Share"),
|
||||
@@ -357,12 +355,8 @@ Kirigami.ApplicationWindow {
|
||||
room: room,
|
||||
user: user,
|
||||
connection: root.connection,
|
||||
}) as UserDetailDialog;
|
||||
// FIXME: The reason why we don't want the focusedWindowItem for the room null case (aka QR codes) is because it will parent it to the soon-to-be-destroyed window item.
|
||||
// But this won't be a problem if we turn it into a Kirigami.Dialog or some other in-scene item, which it really should be.
|
||||
if (room != null) {
|
||||
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
|
||||
}
|
||||
});
|
||||
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
|
||||
dialog.open();
|
||||
}
|
||||
|
||||
@@ -371,7 +365,9 @@ Kirigami.ApplicationWindow {
|
||||
RoomManager.loadInitialRoom();
|
||||
|
||||
if (!Kirigami.Settings.isMobile) {
|
||||
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
|
||||
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'), {
|
||||
connection: root.connection
|
||||
});
|
||||
roomPage.forceActiveFocus();
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ Kirigami.Dialog {
|
||||
/**
|
||||
* @brief Thrown when a user is selected.
|
||||
*/
|
||||
signal userSelected(string userId)
|
||||
signal userSelected
|
||||
|
||||
title: i18nc("@title", "User ID")
|
||||
|
||||
@@ -38,7 +38,7 @@ Kirigami.Dialog {
|
||||
text: i18n("OK")
|
||||
icon.name: "dialog-ok"
|
||||
onTriggered: {
|
||||
root.userSelected(userIdText.text)
|
||||
root.connection.requestDirectChat(userIdText.text);
|
||||
root.accept();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,8 +34,8 @@ Kirigami.Page {
|
||||
enabled: root.model
|
||||
target: root.room
|
||||
function onChanged(): void {
|
||||
root.contentJson = root.model.stateEventContentJson(root.type, root.stateKey);
|
||||
root.sourceText = root.model.stateEventJson(root.type, root.stateKey);
|
||||
root.contentJson = model.stateEventContentJson(root.type, root.stateKey);
|
||||
root.sourceText = model.stateEventJson(root.type, root.stateKey);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,8 +46,8 @@ Kirigami.Page {
|
||||
text: i18nc("@action As in 'edit the state of this room'", "Edit state")
|
||||
icon.name: "document-edit"
|
||||
visible: root.allowEdit
|
||||
enabled: root.room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
|
||||
onTriggered: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog"), {
|
||||
enabled: room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
|
||||
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog.qml"), {
|
||||
room: root.room,
|
||||
type: root.type,
|
||||
stateKey: root.stateKey,
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtCore as Core
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
@@ -23,13 +21,13 @@ Components.AlbumMaximizeComponent {
|
||||
*/
|
||||
required property NeoChatRoom currentRoom
|
||||
|
||||
readonly property string currentEventId: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.EventIdRole)
|
||||
readonly property string currentEventId: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.EventIdRole)
|
||||
|
||||
readonly property var currentAuthor: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.AuthorRole)
|
||||
readonly property var currentAuthor: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.AuthorRole)
|
||||
|
||||
readonly property var currentTime: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.TimeRole)
|
||||
readonly property var currentTime: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.TimeRole)
|
||||
|
||||
readonly property var currentProgressInfo: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.ProgressInfoRole)
|
||||
readonly property var currentProgressInfo: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.ProgressInfoRole)
|
||||
|
||||
actions: [
|
||||
ShareAction {
|
||||
@@ -61,28 +59,28 @@ Components.AlbumMaximizeComponent {
|
||||
|
||||
downloadAction: Components.DownloadAction {
|
||||
onTriggered: {
|
||||
root.currentRoom.downloadFile(root.currentEventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + root.currentRoom.fileNameToDownload(root.currentEventId));
|
||||
currentRoom.downloadFile(root.currentEventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(root.currentEventId));
|
||||
}
|
||||
}
|
||||
|
||||
playAction: Kirigami.Action {
|
||||
onTriggered: {
|
||||
MediaManager.startPlayback();
|
||||
(root.currentItem as Components.VideoMaximizeDelegate).play();
|
||||
currentItem.play();
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: MediaManager
|
||||
function onPlaybackStarted() {
|
||||
if ((root.currentItem as Components.VideoMaximizeDelegate).playbackState === MediaPlayer.PlayingState) {
|
||||
(root.currentItem as Components.VideoMaximizeDelegate).pause();
|
||||
if (currentItem.playbackState === MediaPlayer.PlayingState) {
|
||||
currentItem.pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.currentRoom
|
||||
target: currentRoom
|
||||
|
||||
function onFileTransferProgress(id, progress, total) {
|
||||
if (id == root.currentEventId) {
|
||||
@@ -122,10 +120,10 @@ Components.AlbumMaximizeComponent {
|
||||
|
||||
onOpened: forceActiveFocus()
|
||||
|
||||
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
|
||||
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom, root.currentAuthor)
|
||||
|
||||
onSaveItem: {
|
||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay) as Dialogs.FileDialog;
|
||||
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
|
||||
dialog.selectedFile = currentRoom.fileNameToDownload(root.currentEventId);
|
||||
dialog.open();
|
||||
}
|
||||
@@ -148,7 +146,7 @@ Components.AlbumMaximizeComponent {
|
||||
if (!selectedFile) {
|
||||
return;
|
||||
}
|
||||
root.currentRoom.downloadFile(root.currentEventId, selectedFile);
|
||||
currentRoom.downloadFile(root.currentEventId, selectedFile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// 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 ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
@@ -10,8 +8,11 @@ import QtQuick.Layouts
|
||||
import org.kde.kirigami as Kirigami
|
||||
|
||||
import org.kde.kirigamiaddons.formcard as FormCard
|
||||
import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
|
||||
import Quotient
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.Dialog {
|
||||
@@ -33,7 +34,7 @@ Kirigami.Dialog {
|
||||
}
|
||||
]
|
||||
|
||||
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
|
||||
width: Math.min(applicationWindow().width, Kirigami.Units.gridUnit * 24)
|
||||
title: i18nc("@title: create new poll in the room", "Create Poll")
|
||||
|
||||
contentItem: ColumnLayout {
|
||||
@@ -42,22 +43,22 @@ Kirigami.Dialog {
|
||||
FormCard.FormComboBoxDelegate {
|
||||
id: pollTypeCombo
|
||||
|
||||
text: i18nc("@label", "Poll type:")
|
||||
text: i18n("Poll type:")
|
||||
currentIndex: 0
|
||||
textRole: "text"
|
||||
valueRole: "value"
|
||||
model: [
|
||||
{ value: PollKind.Disclosed, text: i18nc("@item:inlistbox", "Open poll") },
|
||||
{ value: PollKind.Undisclosed, text: i18nc("@item:inlistbox", "Closed poll") }
|
||||
{ value: PollKind.Disclosed, text: i18n("Open poll") },
|
||||
{ value: PollKind.Undisclosed, text: i18n("Closed poll") }
|
||||
]
|
||||
}
|
||||
FormCard.FormTextDelegate {
|
||||
verticalPadding: 0
|
||||
text: pollTypeCombo.currentValue == 0 ? i18nc("@info", "Voters can see the result as soon as they have voted") : i18nc("@info", "Results are revealed only after the poll has closed")
|
||||
text: pollTypeCombo.currentValue == 0 ? i18n("Voters can see the result as soon as they have voted") : i18n("Results are revealed only after the poll has closed")
|
||||
}
|
||||
FormCard.FormTextFieldDelegate {
|
||||
id: questionTextField
|
||||
label: i18nc("@label", "Question:")
|
||||
label: i18n("Question:")
|
||||
}
|
||||
Repeater {
|
||||
id: optionRepeater
|
||||
@@ -116,16 +117,20 @@ Kirigami.Dialog {
|
||||
optionModel.set(optionDelegate.index, {optionText: text})
|
||||
optionModel.allValuesSetChanged()
|
||||
}
|
||||
placeholderText: i18nc("@placeholder", "Enter option")
|
||||
placeholderText: i18n("Enter option")
|
||||
}
|
||||
QQC2.ToolButton {
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
text: i18nc("@action:button", "Remove option")
|
||||
icon.name: "edit-delete-remove"
|
||||
onClicked: optionModel.remove(optionDelegate.index)
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
QQC2.ToolTip.visible: hovered
|
||||
action: Kirigami.Action {
|
||||
id: removeOptionAction
|
||||
text: i18nc("@action:button", "Remove option")
|
||||
icon.name: "edit-delete-remove"
|
||||
onTriggered: optionModel.remove(optionDelegate.index)
|
||||
}
|
||||
QQC2.ToolTip {
|
||||
text: removeOptionAction.text
|
||||
delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
pragma ComponentBehavior: Bound
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigami.delegates as KD
|
||||
import org.kde.kirigamiaddons.components as Components
|
||||
|
||||
import org.kde.neochat
|
||||
@@ -42,23 +41,15 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
delegate: QQC2.ItemDelegate {
|
||||
id: notificationDelegate
|
||||
|
||||
required property string uri
|
||||
required property string authorAvatar
|
||||
required property string authorName
|
||||
required property string roomDisplayName
|
||||
required property string notificationText
|
||||
|
||||
width: parent?.width ?? 0
|
||||
|
||||
onClicked: RoomManager.resolveResource(uri)
|
||||
onClicked: RoomManager.resolveResource(model.uri)
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
Components.Avatar {
|
||||
source: notificationDelegate.authorAvatar
|
||||
name: notificationDelegate.authorName
|
||||
source: model.authorAvatar
|
||||
name: model.authorName
|
||||
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||
implicitWidth: implicitHeight
|
||||
|
||||
@@ -75,7 +66,7 @@ Kirigami.ScrollablePage {
|
||||
QQC2.Label {
|
||||
id: label
|
||||
|
||||
text: notificationDelegate.roomDisplayName
|
||||
text: model.roomDisplayName
|
||||
elide: Text.ElideRight
|
||||
font.weight: Font.Normal
|
||||
textFormat: Text.PlainText
|
||||
@@ -87,9 +78,10 @@ Kirigami.ScrollablePage {
|
||||
QQC2.Label {
|
||||
id: subtitle
|
||||
|
||||
text: notificationDelegate.notificationText
|
||||
text: model.text
|
||||
elide: Text.ElideRight
|
||||
font: Kirigami.Theme.smallFont
|
||||
opacity: root.hasNotifications ? 0.9 : 0.7
|
||||
|
||||
Layout.fillWidth: true
|
||||
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
|
||||
|
||||
@@ -9,6 +9,6 @@ FileDialog {
|
||||
|
||||
signal chosen(string path)
|
||||
|
||||
title: i18nc("@title:dialog", "Select a File")
|
||||
title: i18n("Select a File")
|
||||
onAccepted: root.chosen(selectedFile)
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user