Compare commits

...

57 Commits

Author SHA1 Message Date
James Graham
e8b269869c Some people don't want friends so fix 2024-01-24 16:41:13 +00:00
l10n daemon script
7fd8394253 GIT_SILENT Sync po/docbooks with svn 2024-01-24 01:17:22 +00:00
Tobias Fella
c54a447caf Add appstream developer tag and remove developer_name tag 2024-01-23 22:42:36 +01:00
l10n daemon script
61b009422d GIT_SILENT Sync po/docbooks with svn 2024-01-23 01:18:48 +00:00
l10n daemon script
5a8b0184ea GIT_SILENT Sync po/docbooks with svn 2024-01-22 01:29:04 +00:00
James Graham
f48c2a21d9 Autosearch
Make the user search automatically. This includes a timer to ensure that we aren't constantly pinging the server as the user types, the search is started 0.5s after the user stops typing. The `PublicRoomListModel` is upgraded to work in the same manner as it was architected slightly differently.
2024-01-21 11:24:40 +00:00
l10n daemon script
538cfbee8d GIT_SILENT Sync po/docbooks with svn 2024-01-21 01:17:19 +00:00
James Graham
7666f1c362 Fix the vertical alignment of the notification bubble text 2024-01-20 19:07:27 +00:00
James Graham
4b5d828bf8 The search for friendship
Add the ability to search in the user directory for friends.

This adds an option in roomlist when on the friends tab and opens a search dialog when clicked. The new search model searches the user directory for the given filter term.
2024-01-20 16:13:49 +00:00
Tobias Fella
4bd160cceb Remove workaround for QTBUG 93281
Seems to no longer be required
2024-01-20 16:13:13 +00:00
Joshua Goins
5f56fc1156 Add icon for notification state menu
In Qt6 we can (finally) add icons to QQC Menus!
2024-01-20 13:47:17 +00:00
l10n daemon script
72a2a74395 GIT_SILENT Sync po/docbooks with svn 2024-01-20 01:17:30 +00:00
James Graham
f6a5cc7c25 Generic Search Page
Pull the generic aspects from Room search and join room pages into it's own component. This is done in anticipation of using the new generic search page for a user search functionality.

- `SearchPage` is now used for the generic version with the old one being renamed `RoomSearchPage`
- `JoinRoomPage` is renamed to `ExploreRoomsPage` inline with everywhere else in NeoChat

There is also some cleanup of the code for both search pages in here.
2024-01-19 17:59:45 +00:00
l10n daemon script
80f3bd64b6 GIT_SILENT Sync po/docbooks with svn 2024-01-18 01:18:17 +00:00
Joshua Goins
1f69a96766 Hide the subtitle text for room delegates if there is none
This centers the room name label for room list items, which looks a bit
cleaner than nothing being there at all.
2024-01-17 17:29:57 +00:00
James Graham
f963e06983 Remove the option to merge the room list 2024-01-17 16:58:51 +00:00
l10n daemon script
e6980e2370 GIT_SILENT Sync po/docbooks with svn 2024-01-17 01:19:30 +00:00
James Graham
8e8105d04d Clip QuickSwitcher
Clip QuickSwitcher to stop the delegates overlapping the dialog
2024-01-16 20:08:53 +00:00
Ingo Klöcker
21d9e69712 Require master of ECM
We need the fix for APK packaging with Android NDK r25
2024-01-16 13:57:33 +01:00
l10n daemon script
e0783a3c6e GIT_SILENT Sync po/docbooks with svn 2024-01-16 01:19:20 +00:00
l10n daemon script
3b9337d2a8 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-01-16 01:12:42 +00:00
James Graham
85cda8ffa7 Current Room Messages
Make sure that message delegates are getting the room object directly rather than requiring the assumption that currentRoom is declared somewhere higher up.
2024-01-15 19:47:50 +00:00
l10n daemon script
f1efc1f17d GIT_SILENT Sync po/docbooks with svn 2024-01-15 01:19:09 +00:00
James Graham
0486fa61cd NeoChatConnection signals
Move the signal connects to a function and call from both constructors
2024-01-14 12:25:53 +00:00
Joshua Goins
2247a2a7af Make the search message dialog header way prettier, like it is in KCMs
I think I've heard of this before...
2024-01-14 01:36:59 +00:00
Joshua Goins
08a0fbfd6b Add missing thread roles in SearchModel
This fixes the message search so it works again!
2024-01-14 01:34:43 +00:00
l10n daemon script
898b993b94 GIT_SILENT Sync po/docbooks with svn 2024-01-14 01:30:05 +00:00
James Graham
77e366b179 Why can't we be friends
Update the UX to refer to structure direct chats as friends. The direct chats are pulled into their own tab in the space drawer.

The `UserDetailDialog` is also updated to check whether a direct chat already exists and if not ask to invite as friend.

![image](/uploads/67f13fa8558e704e0acaf7c60e135bbc/image.png)
2024-01-13 21:38:43 +00:00
Tobias Fella
981edc9cf7 Refactor proxy configuration and move to separate file 2024-01-13 17:39:56 +01:00
Tobias Fella
d45aa14348 Refactor some code around connection handling 2024-01-13 13:00:29 +00:00
Tobias Fella
4926488d49 Move notifications button to space drawer.
Since this means that the space drawer can no longer be hidden when there are no spaces,
also make it less empty by adding a button for creating new spaces.
More things will come in the future.

BUG: 479051
2024-01-13 11:28:25 +01:00
l10n daemon script
dcc1935150 GIT_SILENT Sync po/docbooks with svn 2024-01-13 01:24:17 +00:00
Tobias Fella
55364a8eb8 Add basic Itinerary integration
After downloading a file, the model calls the extractor and uses the
JSON to show some basic information about the content and allows to import
the data to Itinerary. This is entirely runtime-optional; no build-time dependencies
are required and nothing changes if the extractor isn't available.
2024-01-12 21:00:14 +00:00
Tobias Fella
70bb06715f Don't crash when calling directChatRemoteUser in something that isn't a direct chat
Can happen e.g. in gammaray
2024-01-12 16:32:49 +01:00
James Graham
ec4aa73e37 Readonly Room
Add readonly property to a room and use it to decide whether to show chatbar, replies and edits

BUG: 479590
2024-01-12 01:59:09 +00:00
Albert Astals Cid
c1d122a717 GIT_SILENT Upgrade release service version to 24.04.70. 2024-01-11 21:36:24 +01:00
Yifan Zhu
f75c194e7c Call signals instead of signal handlers
Directly calling signals is the supported way to send signals.
Calling signal handlers worked in the past, but will be phased out in
the future (https://bugreports.qt.io/browse/QTBUG-120573).
2024-01-11 17:23:08 +00:00
Tobias Fella
ecf93de006 Update copyright year 2024-01-11 18:21:44 +01:00
l10n daemon script
7feb02c7d8 GIT_SILENT Sync po/docbooks with svn 2024-01-11 01:17:30 +00:00
Hannah von Reth
0764fe0dd9 Use craft default targets 2024-01-10 11:47:48 +00:00
Hannah von Reth
50a7138633 Add craft ci targets 2024-01-10 11:47:48 +00:00
l10n daemon script
96a477f91c GIT_SILENT Sync po/docbooks with svn 2024-01-10 02:13:57 +00:00
Tobias Fella
5a6b0f756d Remove broken network error checks
Fixes #529
2024-01-09 19:59:23 +01:00
l10n daemon script
dbbf975f7a GIT_SILENT Sync po/docbooks with svn 2024-01-09 02:10:55 +00:00
l10n daemon script
e06c2d2f93 SVN_SILENT made messages (.desktop file) - always resolve ours
In case of conflict in i18n, keep the version of the branch "ours"
To resolve a particular conflict, "git checkout --ours path/to/file.desktop"
2024-01-09 02:06:01 +00:00
Albert Astals Cid
5dea17b616 GIT_SILENT Upgrade release service version to 24.01.90. 2024-01-09 00:47:19 +01:00
Tobias Fella
63b6d7ebe0 Remove unused includes 2024-01-08 20:41:59 +01:00
l10n daemon script
652106e6a1 GIT_SILENT Sync po/docbooks with svn 2024-01-08 02:16:01 +00:00
Joshua Goins
3e158b3e60 Stop log spam because subtitleText was called without a valid event
It's up to the call site to check if the event is valid before calling
this function, and it prevents tons of log spam because we didn't check
yet.
2024-01-07 21:43:26 +00:00
Tobias Fella
b9ec33dd94 Remove activeConnection from Config
It's not used anymore
2024-01-07 21:25:55 +01:00
Tobias Fella
69bd1202ba Remove another comment 2024-01-07 21:20:04 +01:00
Tobias Fella
f75c09e130 Remove comment 2024-01-07 21:19:25 +01:00
Tobias Fella
683d216f44 Mark ReactionModel as uncreatable 2024-01-07 19:47:19 +01:00
Tobias Fella
c10bcf1764 Move userConsentRequired to NeoChatConnection 2024-01-07 19:44:37 +01:00
Tobias Fella
9e2bf0da26 Port away from Controller::saveWindowGeometry 2024-01-07 19:39:18 +01:00
James Graham
51f7de117d Refactor LinkPreviewer
Refactor `LinkPreviewer` to take an event and put the functions for getting the link in the class itself. This means the functions in `EventHandler` are no longer required.

This mr also sets up `LinkPreviewer` so that it is automatically updated when an event is edited. This includes changing the link if edited, and it can handle a message having a previous link removed or a one added when one didn't exist before.

Also adds test suite.
2024-01-07 18:07:13 +00:00
Tobias Fella
f361f4e2d8 Don't always html-escape user-specified input when serializing a state event body
We don't want this for the room list subtitle
2024-01-07 16:12:45 +00:00
149 changed files with 21582 additions and 17790 deletions

View File

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

View File

@@ -11,3 +11,5 @@ include:
- /gitlab-templates/freebsd-qt6.yml
- /gitlab-templates/flatpak.yml
- /gitlab-templates/craft-android-qt6-apks.yml
- /gitlab-templates/craft-appimage-qt6.yml
- /gitlab-templates/craft-windows-x86-64-qt6.yml

View File

@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "01")
set(RELEASE_SERVICE_VERSION_MICRO "85")
set(RELEASE_SERVICE_VERSION_MINOR "04")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})

View File

@@ -76,3 +76,9 @@ ecm_add_test(
LINK_LIBRARIES neochat Qt::Test
TEST_NAME reactionmodeltest
)
ecm_add_test(
linkpreviewertest.cpp
LINK_LIBRARIES neochat Qt::Test
TEST_NAME linkpreviewertest
)

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "https://matrix.to/#/@alice:example.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "mxc://example.org/SEsfnsuifSDFSSEF",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "testhttps://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,24 @@
{
"timeline": {
"events": [
{
"content": {
"body": "https://kde.org",
"format": "org.matrix.custom.html",
"formatted_body": "https://kde.org",
"msgtype": "m.text"
},
"origin_server_ts": 1704648567967,
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 112
},
"event_id": "$validlink:example.org",
"room_id": "!test:example.org"
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,35 @@
{
"timeline": {
"events": [
{
"content": {
"body": "* ",
"format": "org.matrix.custom.html",
"formatted_body": "no link",
"m.new_content": {
"body": "",
"format": "org.matrix.custom.html",
"formatted_body": "no link",
"msgtype": "m.text"
},
"m.relates_to": {
"event_id": "$validlink:example.org",
"rel_type": "m.replace"
},
"msgtype": "m.text",
"type": "m.room.message"
},
"origin_server_ts": 1704648614969,
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 65
},
"event_id": "$nolink:example.org",
"room_id": "!test:example.org"
}
],
"limited": true,
"prev_batch": "t34-23535_0_0"
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "www.example.org https://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "https://kde.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,14 @@
{
"content": {
"body": "www.example.org",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -0,0 +1,16 @@
{
"content": {
"body": "[Rich Link](https://kde.org)",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://kde.org\">Rich Link</a>",
"msgtype": "m.text"
},
"event_id": "$validlink1:example.org",
"origin_server_ts": 1432735824654,
"room_id": "!test:example.org",
"sender": "@example:example.org",
"type": "m.room.message",
"unsigned": {
"age": 1234
}
}

View File

@@ -65,8 +65,6 @@ private Q_SLOTS:
void nullSubtitle();
void mediaInfo();
void nullMediaInfo();
void linkPreviewer();
void nullLinkPreviewer();
void hasReply();
void nullHasReply();
void replyId();
@@ -399,28 +397,6 @@ void EventHandlerTest::nullMediaInfo()
QCOMPARE(noEventHandler.getMediaInfo(), QVariantMap());
}
void EventHandlerTest::linkPreviewer()
{
auto event = room->messageEvents().at(2).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getLinkPreviewer()->url(), QUrl("https://kde.org"_ls));
event = room->messageEvents().at(0).get();
eventHandler.setEvent(event);
QCOMPARE(eventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::nullLinkPreviewer()
{
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_room set to nullptr.");
QCOMPARE(emptyHandler.getLinkPreviewer(), nullptr);
QTest::ignoreMessage(QtWarningMsg, "getLinkPreviewer called with m_event set to nullptr.");
QCOMPARE(noEventHandler.getLinkPreviewer(), nullptr);
}
void EventHandlerTest::hasReply()
{
auto event = room->messageEvents().at(5).get();

View File

@@ -0,0 +1,104 @@
// 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 <QObject>
#include <QTest>
#include "linkpreviewer.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "utils.h"
#include "testutils.h"
using namespace Quotient;
class LinkPreviewerTest : public QObject
{
Q_OBJECT
private:
Connection *connection = nullptr;
TestUtils::TestRoom *room = nullptr;
private Q_SLOTS:
void initTestCase();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
void editedLink();
};
void LinkPreviewerTest::initTestCase()
{
connection = Connection::makeMockConnection(QStringLiteral("@bob:example.org"));
room = new TestUtils::TestRoom(connection, QStringLiteral("!test:example.org"));
}
void LinkPreviewerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("eventSource");
QTest::addColumn<QUrl>("testOutputLink");
QTest::newRow("plainHttps") << QStringLiteral("test-validplainlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttps") << QStringLiteral("test-validrichlink-event.json") << QUrl("https://kde.org"_ls);
QTest::newRow("plainWww") << QStringLiteral("test-validplainwwwlink-event.json") << QUrl("www.example.org"_ls);
QTest::newRow("multipleHttps") << QStringLiteral("test-multiplelink-event.json") << QUrl("www.example.org"_ls);
}
void LinkPreviewerTest::linkPreviewsMatch()
{
QFETCH(QString, eventSource);
QFETCH(QUrl, testOutputLink);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), testOutputLink);
}
void LinkPreviewerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("eventSource");
QTest::newRow("mxc") << QStringLiteral("test-invalidmxclink-event.json");
QTest::newRow("matrixTo") << QStringLiteral("test-invalidmatrixtolink-event.json");
QTest::newRow("noSpace") << QStringLiteral("test-invalidnospacelink-event.json");
}
void LinkPreviewerTest::linkPreviewsReject()
{
QFETCH(QString, eventSource);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
void LinkPreviewerTest::editedLink()
{
room->syncNewEvents(QStringLiteral("test-linkpreviewerintial-sync.json"));
auto event = eventCast<const RoomMessageEvent>(room->messageEvents().at(0).get());
auto linkPreviewer = LinkPreviewer(room, event);
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), QUrl("https://kde.org"_ls));
room->syncNewEvents(QStringLiteral("test-linkpreviewerreplace-sync.json"));
QCOMPARE(linkPreviewer.empty(), true);
QCOMPARE(linkPreviewer.url(), QUrl());
}
QTEST_MAIN(LinkPreviewerTest)
#include "linkpreviewertest.moc"

View File

@@ -1,6 +1,7 @@
// 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 <Quotient/events/event.h>
#include <Quotient/syncdata.h>
#include "neochatroom.h"
@@ -38,4 +39,17 @@ public:
}
}
};
template<Quotient::EventClass EventT>
inline Quotient::event_ptr_tt<EventT> loadEventFromFile(const QString &eventFileName)
{
if (!eventFileName.isEmpty()) {
QFile testEventFile;
testEventFile.setFileName(QLatin1String(DATA_DIR) + u'/' + eventFileName);
testEventFile.open(QIODevice::ReadOnly);
auto testSyncJson = QJsonDocument::fromJson(testEventFile.readAll()).object();
return Quotient::loadEvent<EventT>(testSyncJson);
}
return nullptr;
}
}

View File

@@ -64,11 +64,6 @@ private Q_SLOTS:
void receiveRichEdited();
void receiveLineSeparator();
void receiveRichCodeUrl();
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
};
void TextHandlerTest::initTestCase()
@@ -523,53 +518,6 @@ void TextHandlerTest::receiveLineSeparator()
QCOMPARE(textHandler.handleRecievePlainText(Qt::PlainText, true), QStringLiteral("foo bar"));
}
void TextHandlerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org"_ls)});
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org"_ls)});
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org"_ls)});
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
<< QList<QUrl>({
QUrl("https://kde.org"_ls),
QUrl("www.example.org"_ls),
});
}
void TextHandlerTest::linkPreviewsMatch()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("testInputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF") << QList<QUrl>();
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org") << QList<QUrl>();
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org") << QList<QUrl>();
}
void TextHandlerTest::linkPreviewsReject()
{
QFETCH(QString, testInputString);
QFETCH(QList<QUrl>, testOutputLinks);
TextHandler testTextHandler;
testTextHandler.setData(testInputString);
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
}
void TextHandlerTest::receiveRichCodeUrl()
{
auto input = QStringLiteral("<code>https://kde.org</code>");

View File

@@ -238,44 +238,11 @@ to provide a convergent experience across multiple platforms.</p>
<categories>
<category>Network</category>
</categories>
<developer_name>The KDE Community</developer_name>
<developer_name xml:lang="ar">مجتمع كِيدِي</developer_name>
<developer_name xml:lang="az">KDE Cəmiyyəti</developer_name>
<developer_name xml:lang="ca">La comunitat KDE</developer_name>
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
<developer_name xml:lang="cs">Komunita KDE</developer_name>
<developer_name xml:lang="de">Die KDE-Gemeinschaft</developer_name>
<developer_name xml:lang="el">Η Κοινότητα του KDE</developer_name>
<developer_name xml:lang="en-GB">The KDE Community</developer_name>
<developer_name xml:lang="eo">La KDE-Komunumo</developer_name>
<developer_name xml:lang="es">La comunidad KDE</developer_name>
<developer_name xml:lang="eu">KDE komunitatea</developer_name>
<developer_name xml:lang="fi">KDE-yhteisö</developer_name>
<developer_name xml:lang="fr">La communauté de KDE</developer_name>
<developer_name xml:lang="gl">A comunidade KDE</developer_name>
<developer_name xml:lang="hu">A KDE Közösség</developer_name>
<developer_name xml:lang="ia">Le communitate de KDE</developer_name>
<developer_name xml:lang="id">Komunitas KDE</developer_name>
<developer_name xml:lang="ie">Li comunité de KDE</developer_name>
<developer_name xml:lang="it">La comunità KDE</developer_name>
<developer_name xml:lang="ka">KDE-ის საზოგადოება</developer_name>
<developer_name xml:lang="ko">KDE 커뮤니티</developer_name>
<developer_name xml:lang="nl">De KDE gemeenschap</developer_name>
<developer_name xml:lang="nn">KDE-fellesskapet</developer_name>
<developer_name xml:lang="pa">ਕੇਡੀਈ ਕਮਿਊਨਟੀ</developer_name>
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
<developer_name xml:lang="pt">A Comunidade do KDE</developer_name>
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
<developer_name xml:lang="ru">Сообщество KDE</developer_name>
<developer_name xml:lang="sk">KDE Komunita</developer_name>
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
<developer_name xml:lang="ta">கே.டீ.யீ. சமூகம்</developer_name>
<developer_name xml:lang="tr">KDE Topluluğu</developer_name>
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
<developer_name xml:lang="x-test">xxThe KDE Communityxx</developer_name>
<developer_name xml:lang="zh-CN">KDE 社区</developer_name>
<developer_name xml:lang="zh-TW">KDE 社群</developer_name>
<developer>
<id>kde.org</id>
<name>The KDE Community</name>
<url>https://kde.org</url>
</developer>
<metadata_license>CC0-1.0</metadata_license>
<project_license>GPL-3.0</project_license>
<custom>

View File

@@ -65,7 +65,7 @@ GenericName[ie]=Cliente de Matrix
GenericName[it]=Client Matrix
GenericName[ka]=Matrix -ის კლიენტი
GenericName[ko]=Matrix 클라이언트
GenericName[lt]=Matrix kliento programą
GenericName[lt]=Matrix kliento programa
GenericName[nl]=Matrix-client
GenericName[nn]=Matrix-klient
GenericName[pa]=ਮੈਟਰਿਕਸ ਕਲਾਈਂਟ

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -144,6 +144,10 @@ add_library(neochat STATIC
models/timelinemodel.cpp
models/timelinemodel.h
enums/pushrule.h
models/itinerarymodel.cpp
models/itinerarymodel.h
proxycontroller.cpp
proxycontroller.h
)
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
@@ -161,11 +165,10 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/UserInfoDesktop.qml
qml/RoomPage.qml
qml/RoomWindow.qml
qml/JoinRoomPage.qml
qml/ExploreRoomsPage.qml
qml/ManualRoomDialog.qml
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/StartChatPage.qml
qml/ImageEditorPage.qml
qml/WelcomePage.qml
qml/General.qml
@@ -267,7 +270,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/EmojiTonesPicker.qml
qml/EmojiDelegate.qml
qml/EmojiGrid.qml
qml/SearchPage.qml
qml/RoomSearchPage.qml
qml/LocationDelegate.qml
qml/LocationChooser.qml
qml/TimelineView.qml
@@ -298,11 +301,16 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/NotificationsView.qml
qml/LoadingDelegate.qml
qml/TimelineEndDelegate.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
qml/UserSearchPage.qml
RESOURCES
qml/confetti.png
qml/glowdot.png
)
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
@@ -316,6 +324,15 @@ ecm_qt_declare_logging_category(neochat
EXPORT NEOCHAT
)
ecm_qt_declare_logging_category(neochat
HEADER "publicroomlist_logging.h"
IDENTIFIER "PublicRoomList"
CATEGORY_NAME "org.kde.neochat.publicroomlistmodel"
DESCRIPTION "Neochat: publicroomlistmodel"
DEFAULT_SEVERITY Info
EXPORT NEOCHAT
)
ecm_qt_declare_logging_category(neochat
HEADER "eventhandler_logging.h"
IDENTIFIER "EventHandling"

8
src/config-neochat.h.in Normal file
View File

@@ -0,0 +1,8 @@
/*
SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
SPDX-License-Identifier: LGPL-2.0-or-later
*/
#pragma once
#define CMAKE_INSTALL_FULL_LIBEXECDIR_KF6 "${KDE_INSTALL_FULL_LIBEXECDIR_KF}"

View File

@@ -7,10 +7,7 @@
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
#include <KWindowConfig>
#include <QFile>
#include <QFileInfo>
#include <QGuiApplication>
#include <QNetworkProxy>
#include <QQuickTextDocument>
@@ -26,15 +23,13 @@
#include <Quotient/csapi/logout.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/eventstats.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/user.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#include "roommanager.h"
#include "windowcontroller.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
@@ -51,7 +46,7 @@ Controller::Controller(QObject *parent)
{
Connection::setRoomType<NeoChatRoom>();
setApplicationProxy();
ProxyController::instance().setApplicationProxy();
#ifndef Q_OS_ANDROID
setQuitOnLastWindowClosed();
@@ -77,8 +72,7 @@ Controller::Controller(QObject *parent)
});
#ifndef Q_OS_WINDOWS
// Setup Unix signal handlers
const auto unixExitHandler = [](int /*sig*/) -> void {
const auto unixExitHandler = [](int) -> void {
QCoreApplication::quit();
};
@@ -104,7 +98,7 @@ Controller::Controller(QObject *parent)
connect(&m_accountRegistry, &AccountRegistry::accountCountChanged, this, [this]() {
if (m_accountRegistry.size() > oldAccountCount) {
auto connection = dynamic_cast<NeoChatConnection *>(m_accountRegistry.accounts()[m_accountRegistry.size() - 1]);
connect(connection, &NeoChatConnection::syncDone, this, [this, connection]() {
connect(connection, &NeoChatConnection::syncDone, this, [connection]() {
NotificationsManager::instance().handleNotifications(connection);
});
connectSingleShot(connection, &NeoChatConnection::syncDone, this, [this, connection] {
@@ -144,7 +138,7 @@ void Controller::addConnection(NeoChatConnection *c)
c->setLazyLoading(true);
connect(c, &NeoChatConnection::syncDone, this, [this, c] {
connect(c, &NeoChatConnection::syncDone, this, [c] {
c->sync(30000);
c->saveState();
});
@@ -152,12 +146,6 @@ void Controller::addConnection(NeoChatConnection *c)
dropConnection(c);
});
connect(c, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequired) {
Q_EMIT userConsentRequired(job->errorUrl());
}
});
c->sync();
Q_EMIT connectionAdded(c);
@@ -174,18 +162,13 @@ void Controller::dropConnection(NeoChatConnection *c)
void Controller::invokeLogin()
{
const auto accounts = SettingsGroup("Accounts"_ls).childGroups();
QString id = NeoChatConfig::self()->activeConnection();
for (const auto &accountId : accounts) {
AccountSettings account{accountId};
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (id.isEmpty()) {
// handle case where the account config is empty
id = accountId;
}
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, id, this, accessTokenLoadingJob](QKeychain::Job *) {
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
@@ -195,28 +178,11 @@ void Controller::invokeLogin()
}
auto connection = new NeoChatConnection(account.homeserver());
connect(connection, &NeoChatConnection::connected, this, [this, connection, id] {
connect(connection, &NeoChatConnection::connected, this, [this, connection] {
connection->loadState();
addConnection(connection);
m_accountsLoading.removeAll(connection->userId());
Q_EMIT accountsLoadingChanged();
if (connection->userId() == id) {
setActiveConnection(connection);
}
});
connect(connection, &NeoChatConnection::loginError, this, [this, connection](const QString &error, const QString &) {
if (error == "Unrecognised access token"_ls) {
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"), {});
connection->logout(false);
} else if (error == "Connection closed"_ls) {
Q_EMIT errorOccured(i18n("Login Failed: %1", error), {});
// Failed due to network connection issue. This might happen when the homeserver is
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
// connect to the homeserver. In this case, we don't want to do logout().
} else {
Q_EMIT errorOccured(i18n("Login Failed: %1", error), {});
connection->logout(true);
}
});
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
Q_EMIT errorOccured(i18n("Network Error: %1", error), {});
@@ -319,36 +285,10 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
if (connection == m_connection) {
return;
}
if (m_connection != nullptr) {
disconnect(m_connection, &NeoChatConnection::syncError, this, nullptr);
disconnect(m_connection, &NeoChatConnection::accountDataChanged, this, nullptr);
}
m_connection = connection;
if (connection != nullptr) {
NeoChatConfig::self()->setActiveConnection(connection->userId());
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
}
});
} else {
NeoChatConfig::self()->setActiveConnection(QString());
}
NeoChatConfig::self()->save();
Q_EMIT activeConnectionChanged();
}
void Controller::saveWindowGeometry()
{
WindowController::instance().saveGeometry();
}
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
{
// HACK: Workaround bug QTBUG 93281
connect(textDocument->textDocument(), SIGNAL(imagesLoaded()), item, SLOT(updateWholeDocument()));
}
void Controller::listenForNotifications()
{
#ifdef HAVE_KUNIFIEDPUSH
@@ -370,36 +310,6 @@ void Controller::listenForNotifications()
#endif
}
void Controller::setApplicationProxy()
{
NeoChatConfig *cfg = NeoChatConfig::self();
QNetworkProxy proxy;
// type match to ProxyType from neochatconfig.kcfg
switch (cfg->proxyType()) {
case 1: // HTTP
proxy.setType(QNetworkProxy::HttpProxy);
proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort());
proxy.setUser(cfg->proxyUser());
proxy.setPassword(cfg->proxyPassword());
break;
case 2: // SOCKS 5
proxy.setType(QNetworkProxy::Socks5Proxy);
proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort());
proxy.setUser(cfg->proxyUser());
proxy.setPassword(cfg->proxyPassword());
break;
case 0: // System Default
default:
// do nothing
break;
}
QNetworkProxy::setApplicationProxy(proxy);
}
bool Controller::isFlatpak() const
{
#ifdef NEOCHAT_FLATPAK

View File

@@ -84,22 +84,8 @@ public:
[[nodiscard]] bool supportSystemTray() const;
/**
* @brief Sets the QNetworkProxy for the application.
*
* @sa QNetworkProxy::setApplicationProxy
*/
Q_INVOKABLE void setApplicationProxy();
bool isFlatpak() const;
/**
* @brief Force a QQuickTextDocument to refresh when images are loaded.
*
* HACK: This is a workaround for QTBUG 93281.
*/
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
/**
* @brief Start listening for notifications in dbus-activated mode.
* These notifications will quit the application when closed.
@@ -134,9 +120,5 @@ Q_SIGNALS:
void connectionAdded(NeoChatConnection *connection);
void connectionDropped(NeoChatConnection *connection);
void activeConnectionChanged();
void userConsentRequired(QUrl url);
void accountsLoadingChanged();
public Q_SLOTS:
void saveWindowGeometry();
};

View File

@@ -355,7 +355,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (e.repeatsState()) {
auto text = i18n("reinvited %1 to the room", subjectName);
if (!e.reason().isEmpty()) {
text += i18nc("Optional reason for an invitation", ": %1") + e.reason().toHtmlEscaped();
text += i18nc("Optional reason for an invitation", ": %1") + (prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
}
return text;
}
@@ -379,7 +379,9 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (!e.newDisplayName()) {
text = i18nc("their refers to a singular user", "cleared their display name");
} else {
text = i18nc("their refers to a singular user", "changed their display name to %1", e.newDisplayName()->toHtmlEscaped());
text = i18nc("their refers to a singular user",
"changed their display name to %1",
prettyPrint ? e.newDisplayName()->toHtmlEscaped() : *e.newDisplayName());
}
}
if (e.isAvatarUpdate()) {
@@ -415,7 +417,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
if (e.reason().isEmpty()) {
return i18n("banned %1 from the room", subjectName);
} else {
return i18n("banned %1 from the room: %2", subjectName, e.reason().toHtmlEscaped());
return i18n("banned %1 from the room: %2", subjectName, prettyPrint ? e.reason().toHtmlEscaped() : e.reason());
}
} else {
return i18n("self-banned from the room");
@@ -431,8 +433,8 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const RoomCanonicalAliasEvent &e) {
return (e.alias().isEmpty()) ? i18n("cleared the room main alias") : i18n("set the room main alias to: %1", e.alias());
},
[](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", e.name().toHtmlEscaped());
[prettyPrint](const RoomNameEvent &e) {
return (e.name().isEmpty()) ? i18n("cleared the room name") : i18n("set the room name to: %1", prettyPrint ? e.name().toHtmlEscaped() : e.name());
},
[prettyPrint, stripNewlines](const RoomTopicEvent &e) {
return (e.topic().isEmpty()) ? i18n("cleared the topic")
@@ -447,14 +449,15 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
[](const EncryptionEvent &) {
return i18n("activated End-to-End Encryption");
},
[](const RoomCreateEvent &e) {
return e.isUpgrade() ? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped())
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped());
[prettyPrint](const RoomCreateEvent &e) {
return e.isUpgrade()
? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()))
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : (prettyPrint ? e.version().toHtmlEscaped() : e.version()));
},
[](const RoomPowerLevelsEvent &) {
return i18nc("'power level' means permission level", "changed the power levels for this room");
},
[](const StateEvent &e) {
[prettyPrint](const StateEvent &e) {
if (e.matrixType() == QLatin1String("m.room.server_acl")) {
return i18n("changed the server access control lists for this room");
}
@@ -471,7 +474,7 @@ QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat f
return e.contentJson()["description"_ls].toString();
}
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
: i18n("updated %1 state for %2", e.matrixType(), e.stateKey().toHtmlEscaped());
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
},
[](const PollStartEvent &e) {
return e.question();
@@ -774,43 +777,6 @@ QVariantMap EventHandler::getMediaInfoFromFileInfo(const EventContent::FileInfo
return mediaInfo;
}
QSharedPointer<LinkPreviewer> EventHandler::getLinkPreviewer() const
{
if (m_room == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_room set to nullptr.";
return nullptr;
}
if (m_event == nullptr) {
qCWarning(EventHandling) << "getLinkPreviewer called with m_event set to nullptr.";
return nullptr;
}
if (!m_event->is<RoomMessageEvent>()) {
return nullptr;
}
QString text;
auto event = eventCast<const RoomMessageEvent>(m_event);
if (event->hasTextContent()) {
auto textContent = static_cast<const EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
TextHandler textHandler;
textHandler.setData(text);
QList<QUrl> links = textHandler.getLinkPreviews();
if (links.size() > 0) {
return QSharedPointer<LinkPreviewer>(new LinkPreviewer(nullptr, m_room, links.size() > 0 ? links[0] : QUrl()));
} else {
return nullptr;
}
}
bool EventHandler::hasReply() const
{
if (m_event == nullptr) {

View File

@@ -231,16 +231,6 @@ public:
*/
QVariantMap getMediaInfo() const;
/**
* @brief Return a LinkPreviewer object for the event.
*
* A nullptr will be returned for any event that doesn't have any links so the
* return should be null checked and an empty LinkPreviewer provided if null.
*
* @sa LinkPreviewer
*/
QSharedPointer<LinkPreviewer> getLinkPreviewer() const;
/**
* @brief Whether the event is a reply to another in the timeline.
*/

View File

@@ -3,25 +3,37 @@
#include "linkpreviewer.h"
#include "controller.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "neochatroom.h"
#include "utils.h"
using namespace Quotient;
LinkPreviewer::LinkPreviewer(QObject *parent, const NeoChatRoom *room, const QUrl &url)
: QObject(parent)
LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event)
: QObject(nullptr)
, m_currentRoom(room)
, m_event(event)
, m_loaded(false)
, m_url(url)
, m_url(linkPreview(event))
{
loadUrlPreview();
if (m_currentRoom) {
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
if (m_event != nullptr && m_currentRoom != nullptr) {
loadUrlPreview();
connect(m_currentRoom, &NeoChatRoom::urlPreviewEnabledChanged, this, &LinkPreviewer::loadUrlPreview);
// Make sure that we react to edits
connect(m_currentRoom, &NeoChatRoom::replacedEvent, this, [this](const Quotient::RoomEvent *newEvent) {
if (m_event->id() == newEvent->id()) {
m_event = eventCast<const Quotient::RoomMessageEvent>(newEvent);
m_url = linkPreview(m_event);
Q_EMIT urlChanged();
loadUrlPreview();
}
});
}
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
}
@@ -51,15 +63,6 @@ QUrl LinkPreviewer::url() const
return m_url;
}
void LinkPreviewer::setUrl(QUrl url)
{
if (url != m_url) {
m_url = url;
urlChanged();
loadUrlPreview();
}
}
void LinkPreviewer::loadUrlPreview()
{
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
@@ -98,4 +101,38 @@ bool LinkPreviewer::empty() const
return m_url.isEmpty();
}
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
{
if (event == nullptr) {
return {};
}
QString text;
if (event->hasTextContent()) {
auto textContent = static_cast<const Quotient::EventContent::TextContent *>(event->content());
if (textContent) {
text = textContent->body;
} else {
text = event->plainBody();
}
} else {
text = event->plainBody();
}
auto data = text.remove(TextRegex::removeRichReply);
auto linksMatch = TextRegex::url.globalMatch(data);
while (linksMatch.hasNext()) {
auto link = linksMatch.next().captured();
if (!link.contains(QStringLiteral("matrix.to"))) {
return QUrl(link);
}
}
return {};
}
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
{
return !linkPreview(event).isEmpty();
}
#include "moc_linkpreviewer.cpp"

View File

@@ -7,6 +7,11 @@
#include <QQmlEngine>
#include <QUrl>
namespace Quotient
{
class RoomMessageEvent;
}
class NeoChatRoom;
/**
@@ -25,7 +30,7 @@ class LinkPreviewer : public QObject
/**
* @brief The URL to get the preview for.
*/
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
Q_PROPERTY(QUrl url READ url NOTIFY urlChanged)
/**
* @brief Whether the preview information has been loaded.
@@ -55,18 +60,25 @@ class LinkPreviewer : public QObject
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
public:
explicit LinkPreviewer(QObject *parent = nullptr, const NeoChatRoom *room = nullptr, const QUrl &url = {});
explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr);
[[nodiscard]] QUrl url() const;
void setUrl(QUrl);
[[nodiscard]] bool loaded() const;
[[nodiscard]] QString title() const;
[[nodiscard]] QString description() const;
[[nodiscard]] QUrl imageSource() const;
[[nodiscard]] bool empty() const;
/**
* @brief Whether the given event has at least 1 pre-viewable link.
*
* A link is only pre-viewable if it is http, https or something starting with www.
*/
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
private:
const NeoChatRoom *m_currentRoom = nullptr;
const NeoChatRoom *m_currentRoom;
const Quotient::RoomMessageEvent *m_event;
bool m_loaded;
QString m_title = QString();
@@ -76,6 +88,14 @@ private:
void loadUrlPreview();
/**
* @brief Return the link to be previewed from the given event.
*
* This function is designed to give only links that should be previewed so
* http, https or something starting with www. The first valid link is returned.
*/
static QUrl linkPreview(const Quotient::RoomMessageEvent *event);
Q_SIGNALS:
void loadedChanged();
void titleChanged();

View File

@@ -130,7 +130,7 @@ int main(int argc, char *argv[])
QStringLiteral(NEOCHAT_VERSION_STRING),
i18n("Matrix client"),
KAboutLicense::GPL_V3,
i18n("© 2018-2020 Black Hat, 2020-2023 KDE Community"));
i18n("© 2018-2020 Black Hat, 2020-2024 KDE Community"));
about.addAuthor(i18n("Carl Schwan"),
i18n("Maintainer"),
QStringLiteral("carl@carlschwan.eu"),

View File

@@ -12,7 +12,6 @@
#include <KLocalizedString>
#include "controller.h"
#include "neochatconnection.h"
#include <Quotient/connection.h>

View File

@@ -4,7 +4,6 @@
#include "actionsmodel.h"
#include "chatbarcache.h"
#include "controller.h"
#include "neochatroom.h"
#include "roommanager.h"
#include <Quotient/events/roommemberevent.h>

View File

@@ -6,10 +6,8 @@
#include <QImage>
#include <QMimeDatabase>
#include "controller.h"
#include "emojimodel.h"
#include <Quotient/connection.h>
#include <Quotient/csapi/account-data.h>
#include <Quotient/csapi/content-repo.h>

View File

@@ -6,9 +6,8 @@
#include <QAbstractListModel>
#include <QQmlEngine>
#include <QRegularExpression>
#include <memory>
class NeoChatConnection;
#include "neochatconnection.h"
struct CustomEmoji {
QString name; // with :semicolons:

View File

@@ -3,7 +3,6 @@
#include "devicesmodel.h"
#include "controller.h"
#include "jobs/neochatdeletedevicejob.h"
#include <QDateTime>

View File

@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "itinerarymodel.h"
#include <QProcess>
#include "config-neochat.h"
#ifndef Q_OS_ANDROID
#include <KIO/ApplicationLauncherJob>
#endif
ItineraryModel::ItineraryModel(QObject *parent)
: QAbstractListModel(parent)
{
}
void ItineraryModel::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
m_connection = connection;
Q_EMIT connectionChanged();
}
NeoChatConnection *ItineraryModel::connection() const
{
return m_connection;
}
QVariant ItineraryModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) {
return {};
}
auto row = index.row();
auto data = m_data[row];
if (role == NameRole) {
if (data[QStringLiteral("@type")] == QStringLiteral("TrainReservation")) {
return data[QStringLiteral("reservationFor")][QStringLiteral("trainNumber")];
}
if (data[QStringLiteral("@type")] == QStringLiteral("LodgingReservation")) {
return data[QStringLiteral("reservationFor")][QStringLiteral("name")];
}
}
if (role == TypeRole) {
return data[QStringLiteral("@type")];
}
if (role == DepartureStationRole) {
return data[QStringLiteral("reservationFor")][QStringLiteral("departureStation")][QStringLiteral("name")];
}
if (role == ArrivalStationRole) {
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalStation")][QStringLiteral("name")];
}
if (role == DepartureTimeRole) {
const auto &time = data[QStringLiteral("reservationFor")][QStringLiteral("departureTime")];
auto dateTime = (time.isString() ? time : time[QStringLiteral("@value")]).toVariant().toDateTime();
if (const auto &timeZone = time[QStringLiteral("timezone")].toString(); timeZone.length() > 0) {
dateTime.setTimeZone(QTimeZone(timeZone.toLatin1().data()));
}
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
}
if (role == ArrivalTimeRole) {
const auto &time = data[QStringLiteral("reservationFor")][QStringLiteral("arrivalTime")];
auto dateTime = (time.isString() ? time : time[QStringLiteral("@value")]).toVariant().toDateTime();
if (const auto &timeZone = time[QStringLiteral("timezone")].toString(); timeZone.length() > 0) {
dateTime.setTimeZone(QTimeZone(timeZone.toLatin1().data()));
}
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
}
if (role == AddressRole) {
const auto &addressData = data[QStringLiteral("reservationFor")][QStringLiteral("address")];
return QStringLiteral("%1 - %2 %3 %4")
.arg(addressData[QStringLiteral("streetAddress")].toString(),
addressData[QStringLiteral("postalCode")].toString(),
addressData[QStringLiteral("addressLocality")].toString(),
addressData[QStringLiteral("addressCountry")].toString());
}
if (role == StartTimeRole) {
auto dateTime = data[QStringLiteral("checkinTime")][QStringLiteral("@value")].toVariant().toDateTime();
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
}
if (role == EndTimeRole) {
auto dateTime = data[QStringLiteral("checkoutTime")][QStringLiteral("@value")].toVariant().toDateTime();
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
}
if (role == DeparturePlatformRole) {
return data[QStringLiteral("reservationFor")][QStringLiteral("departurePlatform")];
}
if (role == ArrivalPlatformRole) {
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalPlatform")];
}
if (role == CoachRole) {
return data[QStringLiteral("reservedTicket")][QStringLiteral("ticketedSeat")][QStringLiteral("seatSection")];
}
if (role == SeatRole) {
return data[QStringLiteral("reservedTicket")][QStringLiteral("ticketedSeat")][QStringLiteral("seatNumber")];
}
return {};
}
int ItineraryModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent);
return m_data.size();
}
QHash<int, QByteArray> ItineraryModel::roleNames() const
{
return {
{NameRole, "name"},
{TypeRole, "type"},
{DepartureStationRole, "departureStation"},
{ArrivalStationRole, "arrivalStation"},
{DepartureTimeRole, "departureTime"},
{ArrivalTimeRole, "arrivalTime"},
{AddressRole, "address"},
{StartTimeRole, "startTime"},
{EndTimeRole, "endTime"},
{DeparturePlatformRole, "departurePlatform"},
{ArrivalPlatformRole, "arrivalPlatform"},
{CoachRole, "coach"},
{SeatRole, "seat"},
};
}
QString ItineraryModel::path() const
{
return m_path;
}
void ItineraryModel::setPath(const QString &path)
{
if (path == m_path) {
return;
}
m_path = path;
Q_EMIT pathChanged();
loadData();
}
void ItineraryModel::loadData()
{
auto process = new QProcess(this);
process->start(QLatin1String(CMAKE_INSTALL_FULL_LIBEXECDIR_KF6) + QLatin1String("/kitinerary-extractor"), {m_path.mid(7)});
connect(process, &QProcess::finished, this, [this, process]() {
auto data = process->readAllStandardOutput();
beginResetModel();
m_data = QJsonDocument::fromJson(data).array();
endResetModel();
});
}
void ItineraryModel::sendToItinerary()
{
#ifndef Q_OS_ANDROID
auto job = new KIO::ApplicationLauncherJob(KService::serviceByDesktopName(QStringLiteral("org.kde.itinerary")));
job->setUrls({QUrl::fromLocalFile(m_path.mid(7))});
job->start();
#endif
}

View File

@@ -0,0 +1,62 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QAbstractListModel>
#include <QPointer>
#include <QQmlEngine>
#include <QString>
#include "neochatconnection.h"
class ItineraryModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
public:
enum Roles {
NameRole = Qt::DisplayRole,
TypeRole,
DepartureStationRole,
ArrivalStationRole,
DepartureTimeRole,
ArrivalTimeRole,
AddressRole,
StartTimeRole,
EndTimeRole,
DeparturePlatformRole,
ArrivalPlatformRole,
CoachRole,
SeatRole,
};
Q_ENUM(Roles)
explicit ItineraryModel(QObject *parent = nullptr);
void setConnection(NeoChatConnection *connection);
NeoChatConnection *connection() const;
QVariant data(const QModelIndex &index, int role) const override;
int rowCount(const QModelIndex &parent = {}) const override;
QHash<int, QByteArray> roleNames() const override;
QString path() const;
void setPath(const QString &path);
Q_INVOKABLE void sendToItinerary();
Q_SIGNALS:
void connectionChanged();
void pathChanged();
private:
QPointer<NeoChatConnection> m_connection;
QJsonArray m_data;
QString m_path;
void loadData();
};

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: GPL-3.0-only
#include "messageeventmodel.h"
#include "linkpreviewer.h"
#include "messageeventmodel_logging.h"
#include "neochatconfig.h"
@@ -22,6 +23,7 @@
#include "eventhandler.h"
#include "events/pollevent.h"
#include "models/reactionmodel.h"
#include "texthandler.h"
using namespace Quotient;
@@ -237,6 +239,10 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
});
connect(m_currentRoom, &Room::replacedEvent, this, [this](const RoomEvent *newEvent) {
refreshLastUserEvents(refreshEvent(newEvent->id()) - timelineBaseIndex());
const RoomMessageEvent *message = eventCast<const RoomMessageEvent>(newEvent);
if (message != nullptr) {
createEventObjects(message);
}
});
connect(m_currentRoom, &Room::updatedEvent, this, [this](const QString &eventId) {
if (eventId.isEmpty()) { // How did we get here?
@@ -720,14 +726,14 @@ void MessageEventModel::createEventObjects(const Quotient::RoomMessageEvent *eve
{
auto eventId = event->id();
EventHandler eventHandler;
eventHandler.setRoom(m_currentRoom);
eventHandler.setEvent(event);
if (auto linkPreviewer = eventHandler.getLinkPreviewer()) {
m_linkPreviewers[eventId] = linkPreviewer;
if (m_linkPreviewers.contains(eventId)) {
if (!LinkPreviewer::hasPreviewableLinks(event)) {
m_linkPreviewers.remove(eventId);
}
} else {
m_linkPreviewers.remove(eventId);
if (LinkPreviewer::hasPreviewableLinks(event)) {
m_linkPreviewers[eventId] = QSharedPointer<LinkPreviewer>(new LinkPreviewer(m_currentRoom, event));
}
}
// ReactionModel handles updates to add and remove reactions, we only need to

View File

@@ -5,6 +5,8 @@
#include <Quotient/connection.h>
#include "publicroomlist_logging.h"
using namespace Quotient;
PublicRoomListModel::PublicRoomListModel(QObject *parent)
@@ -41,7 +43,7 @@ void PublicRoomListModel::setConnection(Connection *conn)
if (job) {
job->abandon();
job = nullptr;
Q_EMIT loadingChanged();
Q_EMIT searchingChanged();
}
if (m_connection) {
@@ -50,7 +52,6 @@ void PublicRoomListModel::setConnection(Connection *conn)
Q_EMIT connectionChanged();
Q_EMIT serverChanged();
Q_EMIT hasMoreChanged();
}
QString PublicRoomListModel::server() const
@@ -71,14 +72,13 @@ void PublicRoomListModel::setServer(const QString &value)
nextBatch = QString();
attempted = false;
rooms.clear();
Q_EMIT loadingChanged();
endResetModel();
if (job) {
job->abandon();
job = nullptr;
Q_EMIT loadingChanged();
Q_EMIT searchingChanged();
}
if (m_connection) {
@@ -86,42 +86,30 @@ void PublicRoomListModel::setServer(const QString &value)
}
Q_EMIT serverChanged();
Q_EMIT hasMoreChanged();
}
QString PublicRoomListModel::keyword() const
QString PublicRoomListModel::searchText() const
{
return m_keyword;
return m_searchText;
}
void PublicRoomListModel::setKeyword(const QString &value)
void PublicRoomListModel::setSearchText(const QString &value)
{
if (m_keyword == value) {
if (m_searchText == value) {
return;
}
m_keyword = value;
beginResetModel();
m_searchText = value;
Q_EMIT searchTextChanged();
nextBatch = QString();
attempted = false;
rooms.clear();
endResetModel();
if (job) {
job->abandon();
job = nullptr;
Q_EMIT loadingChanged();
Q_EMIT searchingChanged();
}
if (m_connection) {
next();
}
Q_EMIT keywordChanged();
Q_EMIT hasMoreChanged();
}
bool PublicRoomListModel::showOnlySpaces() const
@@ -138,15 +126,28 @@ void PublicRoomListModel::setShowOnlySpaces(bool showOnlySpaces)
Q_EMIT showOnlySpacesChanged();
}
void PublicRoomListModel::next(int count)
void PublicRoomListModel::search(int limit)
{
if (count < 1) {
if (limit < 1 || attempted) {
return;
}
if (job) {
qDebug() << "PublicRoomListModel: Other jobs running, ignore";
qCDebug(PublicRoomList) << "Other job running, ignore";
return;
}
next(limit);
}
void PublicRoomListModel::next(int limit)
{
if (m_connection == nullptr || limit < 1) {
return;
}
if (job) {
qCDebug(PublicRoomList) << "Other job running, ignore";
return;
}
@@ -154,11 +155,17 @@ void PublicRoomListModel::next(int count)
if (m_showOnlySpaces) {
roomTypes += QLatin1String("m.space");
}
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword, roomTypes});
Q_EMIT loadingChanged();
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
Q_EMIT searchingChanged();
connect(job, &BaseJob::finished, this, [this] {
attempted = true;
if (!attempted) {
beginResetModel();
rooms.clear();
endResetModel();
attempted = true;
}
if (job->status() == BaseJob::Success) {
nextBatch = job->nextBatch();
@@ -166,14 +173,10 @@ void PublicRoomListModel::next(int count)
this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
rooms.append(job->chunk());
this->endInsertRows();
if (job->nextBatch().isEmpty()) {
Q_EMIT hasMoreChanged();
}
}
this->job = nullptr;
Q_EMIT loadingChanged();
Q_EMIT searchingChanged();
});
}
@@ -184,8 +187,7 @@ QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
}
if (index.row() >= rooms.count()) {
qDebug() << "PublicRoomListModel, something's wrong: index.row() >= "
"rooms.count()";
qCDebug(PublicRoomList) << "something's wrong: index.row() >= rooms.count()";
return {};
}
auto room = rooms.at(index.row());
@@ -271,12 +273,19 @@ int PublicRoomListModel::rowCount(const QModelIndex &parent) const
return rooms.count();
}
bool PublicRoomListModel::hasMore() const
bool PublicRoomListModel::canFetchMore(const QModelIndex &parent) const
{
return !(attempted && nextBatch.isEmpty());
Q_UNUSED(parent)
return !nextBatch.isEmpty();
}
bool PublicRoomListModel::loading() const
void PublicRoomListModel::fetchMore(const QModelIndex &parent)
{
Q_UNUSED(parent)
next();
}
bool PublicRoomListModel::searching() const
{
return job != nullptr;
}

View File

@@ -41,9 +41,9 @@ class PublicRoomListModel : public QAbstractListModel
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged)
/**
* @brief The filter keyword for the list of public rooms.
* @brief The text to search the public room list for.
*/
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
/**
* @brief Whether only space rooms should be shown.
@@ -51,14 +51,9 @@ class PublicRoomListModel : public QAbstractListModel
Q_PROPERTY(bool showOnlySpaces READ showOnlySpaces WRITE setShowOnlySpaces NOTIFY showOnlySpacesChanged)
/**
* @brief Whether the model has more items to load.
* @brief Whether the model is searching.
*/
Q_PROPERTY(bool hasMore READ hasMore NOTIFY hasMoreChanged)
/**
* @biref Whether the model is still loading.
*/
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
public:
/**
@@ -105,31 +100,38 @@ public:
[[nodiscard]] QString server() const;
void setServer(const QString &value);
[[nodiscard]] QString keyword() const;
void setKeyword(const QString &value);
[[nodiscard]] QString searchText() const;
void setSearchText(const QString &searchText);
[[nodiscard]] bool showOnlySpaces() const;
void setShowOnlySpaces(bool showOnlySpaces);
[[nodiscard]] bool hasMore() const;
[[nodiscard]] bool searching() const;
[[nodiscard]] bool loading() const;
/**
* @brief Search the room directory.
*
* @param limit the maximum number of rooms to load.
*/
Q_INVOKABLE void search(int limit = 50);
private:
QPointer<Quotient::Connection> m_connection = nullptr;
QString m_server;
QString m_searchText;
bool m_showOnlySpaces = false;
/**
* @brief Load the next set of rooms.
*
* @param count the maximum number of rooms to load.
* @param limit the maximum number of rooms to load.
*/
Q_INVOKABLE void next(int count = 50);
private:
Quotient::Connection *m_connection = nullptr;
QString m_server;
QString m_keyword;
bool m_showOnlySpaces = false;
void next(int limit = 50);
bool canFetchMore(const QModelIndex &parent) const override;
void fetchMore(const QModelIndex &parent) override;
bool attempted = false;
bool m_loading = false;
bool m_searching = false;
QString nextBatch;
QList<Quotient::PublicRoomsChunk> rooms;
@@ -139,8 +141,7 @@ private:
Q_SIGNALS:
void connectionChanged();
void serverChanged();
void keywordChanged();
void searchTextChanged();
void showOnlySpacesChanged();
void hasMoreChanged();
void loadingChanged();
void searchingChanged();
};

View File

@@ -11,7 +11,6 @@
#include <Quotient/csapi/pushrules.h>
#include <Quotient/jobs/basejob.h>
#include "controller.h"
#include "neochatconfig.h"
#include <KLazyLocalizedString>

View File

@@ -22,6 +22,7 @@ class ReactionModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_UNCREATABLE("")
public:
/**

View File

@@ -3,15 +3,12 @@
#include "roomlistmodel.h"
#include "controller.h"
#include "eventhandler.h"
#include "neochatconfig.h"
#include "neochatroom.h"
#include "roommanager.h"
#include "spacehierarchycache.h"
#include <Quotient/user.h>
#include <QDebug>
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
#ifndef Q_OS_ANDROID
@@ -23,7 +20,6 @@
#include <KLocalizedString>
#include <QGuiApplication>
#include <utility>
using namespace Quotient;
@@ -349,7 +345,7 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
return m_categoryVisibility.value(data(index, CategoryRole).toInt(), true);
}
if (role == SubtitleTextRole) {
if (room->lastEventIsSpoiler()) {
if (room->lastEvent() == nullptr || room->lastEventIsSpoiler()) {
return QString();
}
EventHandler eventHandler;
@@ -372,6 +368,9 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
if (role == ReplacementIdRole) {
return room->successorId();
}
if (role == IsDirectChat) {
return room->isDirectChat();
}
return QVariant();
}
@@ -405,6 +404,7 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
roles[IsSpaceRole] = "isSpace";
roles[RoomIdRole] = "roomId";
roles[IsChildSpaceRole] = "isChildSpace";
roles[IsDirectChat] = "isDirectChat";
return roles;
}
@@ -416,7 +416,7 @@ QString RoomListModel::categoryName(int category)
case NeoChatRoomType::Favorite:
return i18n("Favorite");
case NeoChatRoomType::Direct:
return i18n("Direct Messages");
return i18n("Friends");
case NeoChatRoomType::Normal:
return i18n("Normal");
case NeoChatRoomType::Deprioritized:

View File

@@ -3,8 +3,6 @@
#pragma once
#include <Quotient/events/roomevent.h>
#include <QAbstractListModel>
#include <QQmlEngine>
@@ -79,6 +77,7 @@ public:
IsSpaceRole, /**< Whether the room is a space. */
IsChildSpaceRole, /**< Whether this space is a child of a different space. */
ReplacementIdRole, /**< The room id of the room replacing this one, if any. */
IsDirectChat, /**< Whether this room is a direct chat. */
};
Q_ENUM(EventRoles)

View File

@@ -36,7 +36,7 @@ void SearchModel::setSearchText(const QString &searchText)
void SearchModel::search()
{
Q_ASSERT(m_connection);
Q_ASSERT(m_room);
setSearching(true);
if (m_job) {
m_job->abandon();
@@ -62,7 +62,7 @@ void SearchModel::search()
};
auto job = m_connection->callApi<SearchJob>(SearchJob::Categories{criteria});
auto job = m_room->connection()->callApi<SearchJob>(SearchJob::Categories{criteria});
m_job = job;
connect(job, &BaseJob::finished, this, [this, job] {
beginResetModel();
@@ -74,17 +74,6 @@ void SearchModel::search()
});
}
Connection *SearchModel::connection() const
{
return m_connection;
}
void SearchModel::setConnection(Connection *connection)
{
m_connection = connection;
Q_EMIT connectionChanged();
}
QVariant SearchModel::data(const QModelIndex &index, int role) const
{
auto row = index.row();
@@ -136,6 +125,10 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
return eventHandler.isHighlighted();
case EventIdRole:
return eventHandler.getId();
case IsThreadedRole:
return eventHandler.isThreaded();
case ThreadRootRole:
return eventHandler.threadRoot();
}
return DelegateType::Message;
}
@@ -181,6 +174,8 @@ QHash<int, QByteArray> SearchModel::roleNames() const
{MimeTypeRole, "mimeType"},
{ShowLinkPreviewRole, "showLinkPreview"},
{LinkPreviewRole, "linkPreview"},
{IsThreadedRole, "isThreaded"},
{ThreadRootRole, "threadRoot"},
};
}

View File

@@ -31,11 +31,6 @@ class SearchModel : public QAbstractListModel
*/
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
/**
* @brief The current connection that the model is using to search for messages.
*/
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The current room that the search is being done from.
*/
@@ -85,6 +80,8 @@ public:
MimeTypeRole,
ShowLinkPreviewRole,
LinkPreviewRole,
IsThreadedRole,
ThreadRootRole,
};
Q_ENUM(Roles)
explicit SearchModel(QObject *parent = nullptr);
@@ -92,9 +89,6 @@ public:
QString searchText() const;
void setSearchText(const QString &searchText);
Quotient::Connection *connection() const;
void setConnection(Quotient::Connection *connection);
NeoChatRoom *room() const;
void setRoom(NeoChatRoom *room);
@@ -128,7 +122,6 @@ public:
Q_SIGNALS:
void searchTextChanged();
void connectionChanged();
void roomChanged();
void searchingChanged();
@@ -139,7 +132,6 @@ private:
void setSearching(bool searching);
QString m_searchText;
Quotient::Connection *m_connection = nullptr;
NeoChatRoom *m_room = nullptr;
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
Quotient::SearchJob *m_job = nullptr;

View File

@@ -3,8 +3,6 @@
#include "serverlistmodel.h"
#include "controller.h"
#include <Quotient/connection.h>
#include <QDebug>
@@ -13,6 +11,8 @@
#include <KConfigGroup>
#include <KSharedConfig>
#include "neochatconnection.h"
ServerListModel::ServerListModel(QObject *parent)
: QAbstractListModel(parent)
{

View File

@@ -83,6 +83,21 @@ bool SortFilterRoomListModel::filterAcceptsRow(int source_row, const QModelIndex
{
Q_UNUSED(source_parent);
bool acceptRoom =
sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() == false;
bool isDirectChat = sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsDirectChat).toBool();
// In `show direct chats` mode we only care about whether or not it's a direct chat or if the filter string matches.'
if (m_mode == DirectChats) {
return isDirectChat && acceptRoom;
}
// When not in `show direct chats` mode, filter them out.
if (isDirectChat && m_mode == Rooms) {
return false;
}
if (sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() == QStringLiteral("upgraded")
&& dynamic_cast<RoomListModel *>(sourceModel())
->connection()
@@ -90,10 +105,6 @@ bool SortFilterRoomListModel::filterAcceptsRow(int source_row, const QModelIndex
return false;
}
bool acceptRoom =
sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() == false;
if (m_activeSpaceId.isEmpty()) {
return acceptRoom;
} else {
@@ -116,4 +127,20 @@ void SortFilterRoomListModel::setActiveSpaceId(const QString &spaceId)
invalidate();
}
SortFilterRoomListModel::Mode SortFilterRoomListModel::mode() const
{
return m_mode;
}
void SortFilterRoomListModel::setMode(SortFilterRoomListModel::Mode mode)
{
if (m_mode == mode) {
return;
}
m_mode = mode;
Q_EMIT modeChanged();
invalidate();
}
#include "moc_sortfilterroomlistmodel.cpp"

View File

@@ -47,6 +47,11 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
*/
Q_PROPERTY(QString activeSpaceId READ activeSpaceId WRITE setActiveSpaceId NOTIFY activeSpaceIdChanged)
/**
* @brief Whether only direct chats should be shown.
*/
Q_PROPERTY(Mode mode READ mode WRITE setMode NOTIFY modeChanged)
public:
enum RoomSortOrder {
Alphabetical,
@@ -55,6 +60,13 @@ public:
};
Q_ENUM(RoomSortOrder)
enum Mode {
Rooms,
DirectChats,
All,
};
Q_ENUM(Mode)
explicit SortFilterRoomListModel(QObject *parent = nullptr);
void setRoomSortOrder(RoomSortOrder sortOrder);
@@ -66,6 +78,9 @@ public:
QString activeSpaceId() const;
void setActiveSpaceId(const QString &spaceId);
Mode mode() const;
void setMode(Mode mode);
protected:
/**
* @brief Returns true if the value of source_left is less than source_right.
@@ -85,9 +100,11 @@ Q_SIGNALS:
void roomSortOrderChanged();
void filterTextChanged();
void activeSpaceIdChanged();
void modeChanged();
private:
RoomSortOrder m_sortOrder = Categories;
Mode m_mode = All;
QString m_filterText;
QString m_activeSpaceId;
};

View File

@@ -7,7 +7,7 @@
#include <Quotient/jobs/basejob.h>
#include <Quotient/room.h>
#include "controller.h"
#include "neochatconnection.h"
SpaceChildrenModel::SpaceChildrenModel(QObject *parent)
: QAbstractItemModel(parent)

View File

@@ -3,7 +3,7 @@
#include "spacetreeitem.h"
#include "controller.h"
#include "neochatconnection.h"
SpaceTreeItem::SpaceTreeItem(NeoChatConnection *connection,
SpaceTreeItem *parent,

View File

@@ -26,7 +26,6 @@ void UserDirectoryListModel::setConnection(Connection *conn)
beginResetModel();
m_limited = false;
attempted = false;
users.clear();
@@ -37,53 +36,50 @@ void UserDirectoryListModel::setConnection(Connection *conn)
endResetModel();
m_connection = conn;
if (job) {
job->abandon();
job = nullptr;
}
Q_EMIT connectionChanged();
Q_EMIT limitedChanged();
if (m_job) {
m_job->abandon();
m_job = nullptr;
Q_EMIT searchingChanged();
}
}
QString UserDirectoryListModel::keyword() const
QString UserDirectoryListModel::searchText() const
{
return m_keyword;
return m_searchText;
}
void UserDirectoryListModel::setKeyword(const QString &value)
void UserDirectoryListModel::setSearchText(const QString &value)
{
if (m_keyword == value) {
if (m_searchText == value) {
return;
}
m_keyword = value;
m_searchText = value;
Q_EMIT searchTextChanged();
if (m_job) {
m_job->abandon();
m_job = nullptr;
Q_EMIT searchingChanged();
}
m_limited = false;
attempted = false;
if (job) {
job->abandon();
job = nullptr;
}
Q_EMIT keywordChanged();
Q_EMIT limitedChanged();
}
bool UserDirectoryListModel::limited() const
bool UserDirectoryListModel::searching() const
{
return m_limited;
return m_job != nullptr;
}
void UserDirectoryListModel::search(int count)
void UserDirectoryListModel::search(int limit)
{
if (count < 1) {
if (limit < 1) {
return;
}
if (job) {
if (m_job) {
qDebug() << "UserDirectoryListModel: Other jobs running, ignore";
return;
@@ -93,25 +89,22 @@ void UserDirectoryListModel::search(int count)
return;
}
job = m_connection->callApi<SearchUserDirectoryJob>(m_keyword, count);
m_job = m_connection->callApi<SearchUserDirectoryJob>(m_searchText, limit);
Q_EMIT searchingChanged();
connect(job, &BaseJob::finished, this, [this] {
connect(m_job, &BaseJob::finished, this, [this] {
attempted = true;
if (job->status() == BaseJob::Success) {
auto users = job->results();
if (m_job->status() == BaseJob::Success) {
auto users = m_job->results();
this->beginResetModel();
this->users = users;
this->m_limited = job->limited();
this->endResetModel();
}
this->job = nullptr;
Q_EMIT limitedChanged();
this->m_job = nullptr;
Q_EMIT searchingChanged();
});
}
@@ -127,7 +120,7 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
return {};
}
auto user = users.at(index.row());
if (role == NameRole) {
if (role == DisplayNameRole) {
auto displayName = user.displayName;
if (!displayName.isEmpty()) {
return displayName;
@@ -142,18 +135,17 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
}
if (role == AvatarRole) {
auto avatarUrl = user.avatarUrl;
if (avatarUrl.isEmpty()) {
return QString();
if (avatarUrl.isEmpty() || !m_connection) {
return QUrl();
}
return avatarUrl.url().remove(0, 6);
return m_connection->makeMediaUrl(avatarUrl);
}
if (role == UserIDRole) {
return user.userId;
}
if (role == DirectChatsRole) {
if (role == DirectChatExistsRole) {
if (!m_connection) {
return QStringList();
return false;
};
auto userObj = m_connection->user(user.userId);
@@ -162,11 +154,11 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
if (userObj && directChats.contains(userObj)) {
auto directChatsForUser = directChats.values(userObj);
if (!directChatsForUser.isEmpty()) {
return QVariant::fromValue(directChatsForUser);
return true;
}
}
return QStringList();
return false;
}
return {};
@@ -176,10 +168,10 @@ QHash<int, QByteArray> UserDirectoryListModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NameRole] = "name";
roles[AvatarRole] = "avatar";
roles[UserIDRole] = "userID";
roles[DirectChatsRole] = "directChats";
roles[DisplayNameRole] = "displayName";
roles[AvatarRole] = "avatarUrl";
roles[UserIDRole] = "userId";
roles[DirectChatExistsRole] = "directChatExists";
return roles;
}

View File

@@ -35,24 +35,24 @@ class UserDirectoryListModel : public QAbstractListModel
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The keyword to use in the search.
* @brief The text to search the public room list for.
*/
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
/**
* @brief Whether the current results have been truncated.
* @brief Whether the model is searching.
*/
Q_PROPERTY(bool limited READ limited NOTIFY limitedChanged)
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
public:
/**
* @brief Defines the model roles.
*/
enum EventRoles {
NameRole = Qt::DisplayRole + 1, /**< The user's display name. */
DisplayNameRole = Qt::DisplayRole, /**< The user's display name. */
AvatarRole, /**< The source URL for the user's avatar. */
UserIDRole, /**< Matrix ID of the user. */
DirectChatsRole, /**< A list of direct chat matrix IDs with the user. */
DirectChatExistsRole, /**< Whether there is already a direct chat with the user. */
};
explicit UserDirectoryListModel(QObject *parent = nullptr);
@@ -60,17 +60,17 @@ public:
[[nodiscard]] Quotient::Connection *connection() const;
void setConnection(Quotient::Connection *conn);
[[nodiscard]] QString keyword() const;
void setKeyword(const QString &value);
[[nodiscard]] QString searchText() const;
void setSearchText(const QString &searchText);
[[nodiscard]] bool limited() const;
[[nodiscard]] bool searching() const;
/**
* @brief Get the given role value at the given index.
*
* @sa QAbstractItemModel::data
*/
[[nodiscard]] QVariant data(const QModelIndex &index, int role = NameRole) const override;
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
/**
* @brief Number of rows in the model.
@@ -87,23 +87,23 @@ public:
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
/**
* @brief Start the user search.
* @brief Search the user directory.
*
* @param limit the maximum number of rooms to load.
*/
Q_INVOKABLE void search(int count = 50);
Q_INVOKABLE void search(int limit = 50);
Q_SIGNALS:
void connectionChanged();
void keywordChanged();
void limitedChanged();
void searchTextChanged();
void searchingChanged();
private:
Quotient::Connection *m_connection = nullptr;
QString m_keyword;
bool m_limited = false;
QString m_searchText;
bool attempted = false;
QList<Quotient::SearchUserDirectoryJob::User> users;
Quotient::SearchUserDirectoryJob *job = nullptr;
Quotient::SearchUserDirectoryJob *m_job = nullptr;
};

View File

@@ -181,12 +181,14 @@ Name[eu]=Gonbidapen berria
Name[fi]=Uusi kutsu
Name[fr]=Nouvelle invitation
Name[gl]=Novo convite
Name[hu]=Új meghívó
Name[ia]=Nove invitation
Name[id]=Undangan Baru
Name[ie]=Nov invitation
Name[it]=Nuovo invito
Name[ka]=ახალი მოსაწვევი
Name[ko]=새 초대장
Name[lt]=Naujas pakvietimas
Name[nl]=Nieuwe uitnodiging
Name[nn]=Ny invitasjon
Name[pa]=ਨਵਾਂ ਸੱਦਾ
@@ -217,12 +219,14 @@ Comment[eu]=Gela baterako gonbidapen berri bat dago
Comment[fi]=Uusi kutsu huoneeseen
Comment[fr]=Il y a une nouvelle invitation dans un salon.
Comment[gl]=Tes un novo convite para unha sala.
Comment[hu]=Új meghívó érkezett egy szobába
Comment[ia]=Il ha un nove invitation a un sala
Comment[id]=Ada undangan baru ke sebuah ruangan
Comment[ie]=Vu have un nov invitation a un chambre
Comment[it]=È presente un nuovo invito a una stanza
Comment[ka]=გაქვთ ახალი ოთახის მოსაწვევი
Comment[ko]=새로운 대화방 초대장을 받음
Comment[lt]=Yra naujas pakvietimas į kambarį
Comment[nl]=Er is een nieuwe uitnodiging naar een room
Comment[nn]=Du har ein ny invitasjon til eit rom
Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ

View File

@@ -11,9 +11,6 @@
<entry name="OpenRoom" type="String">
<label>Latest opened room</label>
</entry>
<entry name="ActiveConnection" type="String">
<label>Latest active connection</label>
</entry>
<entry name="ColorScheme" type="String">
<label>Color scheme</label>
</entry>
@@ -25,10 +22,6 @@
<label>Background transparency value</label>
<default>0.3</default>
</entry>
<entry name="MergeRoomList" type="bool">
<label>Merge Room Lists</label>
<default>false</default>
</entry>
<entry name="AllowQuickEdit" type="bool">
<label>Use s/text/replacement syntax to edit your last message.</label>
<default>false</default>

View File

@@ -10,6 +10,8 @@
#include "jobs/neochatdeactivateaccountjob.h"
#include "roommanager.h"
#include <Quotient/connection.h>
#include <Quotient/quotient_common.h>
#include <qt6keychain/keychain.h>
#include <KLocalizedString>
@@ -17,7 +19,9 @@
#include <Quotient/csapi/content-repo.h>
#include <Quotient/csapi/profile.h>
#include <Quotient/database.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/room.h>
#include <Quotient/settings.h>
#include <Quotient/user.h>
@@ -32,6 +36,17 @@ using namespace Qt::StringLiterals;
NeoChatConnection::NeoChatConnection(QObject *parent)
: Connection(parent)
{
connectSignals();
}
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
: Connection(server, parent)
{
connectSignals();
}
void NeoChatConnection::connectSignals()
{
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
if (type == QLatin1String("org.kde.neochat.account_label")) {
@@ -44,14 +59,42 @@ NeoChatConnection::NeoChatConnection(QObject *parent)
connect(this, &NeoChatConnection::networkError, this, [this]() {
setIsOnline(false);
});
}
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
: Connection(server, parent)
{
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
if (type == QLatin1String("org.kde.neochat.account_label")) {
Q_EMIT labelChanged();
connect(this, &NeoChatConnection::requestFailed, this, [this](BaseJob *job) {
if (job->error() == BaseJob::UserConsentRequired) {
Q_EMIT userConsentRequired(job->errorUrl());
}
});
connect(this, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
}
});
connect(this, &NeoChatConnection::directChatsListChanged, this, [this](DirectChatsMap additions, DirectChatsMap removals) {
Q_EMIT directChatInvitesChanged();
for (const auto &chatId : additions) {
if (const auto chat = room(chatId)) {
connect(chat, &Room::unreadStatsChanged, this, [this]() {
Q_EMIT directChatNotificationsChanged();
});
}
}
for (const auto &chatId : removals) {
if (const auto chat = room(chatId)) {
disconnect(chat, &Room::unreadStatsChanged, this, nullptr);
}
}
});
connect(this, &NeoChatConnection::joinedRoom, this, [this](Room *room) {
if (room->isDirectChat()) {
connect(room, &Room::unreadStatsChanged, this, [this]() {
Q_EMIT directChatNotificationsChanged();
});
}
});
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
Q_UNUSED(room)
if (prev && prev->isDirectChat()) {
Q_EMIT directChatInvitesChanged();
}
});
}
@@ -233,6 +276,20 @@ void NeoChatConnection::createSpace(const QString &name, const QString &topic, c
});
}
bool NeoChatConnection::directChatExists(Quotient::User *user)
{
return directChats().contains(user);
}
void NeoChatConnection::openOrCreateDirectChat(const QString &userId)
{
if (auto user = this->user(userId)) {
openOrCreateDirectChat(user);
} else {
qWarning() << "openOrCreateDirectChat: Couldn't get user object for ID " << userId << ", unable to open/request direct chat.";
}
}
void NeoChatConnection::openOrCreateDirectChat(User *user)
{
const auto existing = directChats();
@@ -247,6 +304,32 @@ void NeoChatConnection::openOrCreateDirectChat(User *user)
requestDirectChat(user);
}
qsizetype NeoChatConnection::directChatNotifications() const
{
qsizetype notifications = 0;
QStringList added; // The same ID can be in the list multiple times.
for (const auto &chatId : directChats()) {
if (!added.contains(chatId)) {
if (const auto chat = room(chatId)) {
notifications += chat->notificationCount();
added += chatId;
}
}
}
return notifications;
}
bool NeoChatConnection::directChatInvites() const
{
auto inviteRooms = rooms(JoinState::Invite);
for (const auto inviteRoom : inviteRooms) {
if (inviteRoom->isDirectChat()) {
return true;
}
}
return false;
}
QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
{
#ifdef HAVE_KUNIFIEDPUSH

View File

@@ -27,6 +27,16 @@ class NeoChatConnection : public Quotient::Connection
Q_PROPERTY(QString deviceKey READ deviceKey CONSTANT)
Q_PROPERTY(QString encryptionKey READ encryptionKey CONSTANT)
/**
* @brief The total number of notifications for all direct chats.
*/
Q_PROPERTY(qsizetype directChatNotifications READ directChatNotifications NOTIFY directChatNotificationsChanged)
/**
* @brief Whether there is at least one invite to a direct chat.
*/
Q_PROPERTY(bool directChatInvites READ directChatInvites NOTIFY directChatInvitesChanged)
/**
* @brief Whether NeoChat is currently able to connect to the server.
*/
@@ -80,12 +90,27 @@ public:
Q_INVOKABLE void createSpace(const QString &name, const QString &topic, const QString &parent = {}, bool setChildParent = false);
/**
* @brief Join a direct chat with the given user.
* @brief Whether a direct chat with the user exists.
*/
Q_INVOKABLE bool directChatExists(Quotient::User *user);
/**
* @brief Join a direct chat with the given user ID.
*
* If a direct chat with the user doesn't exist one is created and then joined.
*/
Q_INVOKABLE void openOrCreateDirectChat(const QString &userId);
/**
* @brief Join a direct chat with the given user object.
*
* If a direct chat with the user doesn't exist one is created and then joined.
*/
Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user);
qsizetype directChatNotifications() const;
bool directChatInvites() const;
// note: this is intentionally a copied QString because
// the reference could be destroyed before the task is finished
QCoro::Task<void> setupPushNotifications(QString endpoint);
@@ -97,10 +122,15 @@ public:
Q_SIGNALS:
void labelChanged();
void directChatNotificationsChanged();
void directChatInvitesChanged();
void isOnlineChanged();
void passwordStatus(NeoChatConnection::PasswordStatus status);
void userConsentRequired(QUrl url);
private:
bool m_isOnline = true;
void setIsOnline(bool isOnline);
void connectSignals();
};

View File

@@ -4,15 +4,10 @@
#include "neochatroom.h"
#include <QFileInfo>
#include <QGuiApplication>
#include <QMetaObject>
#include <QMimeDatabase>
#include <QPalette>
#include <QTemporaryFile>
#include <QTextDocument>
#include <QMediaMetaData>
#include <QMediaPlayer>
#include <QMimeDatabase>
#include <QTemporaryFile>
#include <Quotient/jobs/basejob.h>
#include <Quotient/user.h>
@@ -35,13 +30,11 @@
#include <Quotient/events/roommemberevent.h>
#include <Quotient/events/roompowerlevelsevent.h>
#include <Quotient/events/simplestateevents.h>
#include <Quotient/events/stickerevent.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include "chatbarcache.h"
#include "clipboard.h"
#include "controller.h"
#include "eventhandler.h"
#include "events/joinrulesevent.h"
#include "events/pollevent.h"
@@ -53,8 +46,6 @@
#include "urlhelper.h"
#include "utils.h"
#include <KConfig>
#include <KConfigGroup>
#ifndef Q_OS_ANDROID
#include <KIO/Job>
#include <KIO/JobTracker>
@@ -123,6 +114,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
Q_EMIT parentIdsChanged();
Q_EMIT canonicalParentChanged();
Q_EMIT joinRuleChanged();
Q_EMIT readOnlyChanged();
});
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
connect(this, &Room::changed, this, [this]() {
@@ -671,6 +663,11 @@ bool NeoChatRoom::isInvite() const
return joinState() == JoinState::Invite;
}
bool NeoChatRoom::readOnly() const
{
return !canSendEvent("m.room.message"_ls);
}
bool NeoChatRoom::isUserBanned(const QString &user) const
{
auto roomMemberEvent = currentState().get<RoomMemberEvent>(user);
@@ -1829,7 +1826,11 @@ int NeoChatRoom::maxRoomVersion() const
Quotient::User *NeoChatRoom::directChatRemoteUser() const
{
return connection()->directChatUsers(this)[0];
auto users = connection()->directChatUsers(this);
if (users.isEmpty()) {
return nullptr;
}
return users[0];
}
void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)

View File

@@ -133,6 +133,11 @@ class NeoChatRoom : public Quotient::Room
*/
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
/**
* @brief Whether the local user can send messages in the room.
*/
Q_PROPERTY(bool readOnly READ readOnly NOTIFY readOnlyChanged)
/**
* @brief The current join rule for the room as a QString.
*
@@ -552,6 +557,8 @@ public:
bool isInvite() const;
bool readOnly() const;
Q_INVOKABLE void clearInvitationNotification();
[[nodiscard]] QString joinRule() const;
@@ -809,6 +816,7 @@ Q_SIGNALS:
void canonicalParentChanged();
void lastActiveTimeChanged();
void isInviteChanged();
void readOnlyChanged();
void displayNameChanged();
void pushNotificationStateChanged(PushNotificationState::State state);
void showMessage(MessageType messageType, const QString &message);

View File

@@ -21,7 +21,6 @@
#endif
#include "controller.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "roommanager.h"

View File

@@ -56,12 +56,14 @@ Comment[eu]=Bilatu gelak NeoChat-en
Comment[fi]=Etsi huoneita NeoChatissä
Comment[fr]=Trouver des salons dans NeoChat
Comment[gl]=Atopa salas en NeoChat.
Comment[hu]=Szobák keresése a NeoChatben
Comment[ia]=Trova salas in NeoChat
Comment[id]=Cari ruangan di NeoChat
Comment[ie]=Trovar chambres in NeoChat
Comment[it]=Trova stanze in NeoChat
Comment[ka]=იპოვე ოთახები NeoChat-ში
Comment[ko]=NeoChat에서 대화방 찾기
Comment[lt]=Rasti kambarius NeoChat
Comment[nl]=Rooms zoeken in NeoChat
Comment[nn]=Finn rom i NeoChat
Comment[pl]=Znajdź pokoje w NeoChat

35
src/proxycontroller.cpp Normal file
View File

@@ -0,0 +1,35 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "proxycontroller.h"
#include <QNetworkProxy>
#include "neochatconfig.h"
void ProxyController::setApplicationProxy()
{
auto cfg = NeoChatConfig::self();
QNetworkProxy proxy;
switch (cfg->proxyType()) {
case 1:
proxy.setType(QNetworkProxy::HttpProxy);
break;
case 2:
proxy.setType(QNetworkProxy::Socks5Proxy);
break;
default:
break;
}
proxy.setHostName(cfg->proxyHost());
proxy.setPort(cfg->proxyPort());
proxy.setUser(cfg->proxyUser());
proxy.setPassword(cfg->proxyPassword());
QNetworkProxy::setApplicationProxy(proxy);
}
ProxyController::ProxyController(QObject *parent)
: QObject(parent)
{
}

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