Compare commits

..

1 Commits

Author SHA1 Message Date
Justin Zobel
ae155805e9 CI - Flatpak - Update Runtime/SDK to 6.7 2024-04-07 22:49:04 +00:00
283 changed files with 36253 additions and 52159 deletions

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "6.6",
"runtime-version": "6.7",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [
@@ -110,7 +110,7 @@
{
"type": "git",
"url": "https://github.com/quotient-im/libQuotient.git",
"branch": "0.8.x",
"branch": "dev",
"disable-submodules": true
}
],

1
.gitignore vendored
View File

@@ -12,4 +12,3 @@ kate.project.ctags.*
.idea/
cmake-build-*
src/res.generated.qrc
.qmlls.ini

View File

@@ -28,7 +28,6 @@ Dependencies:
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
'frameworks/kcrash': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@latest-kf6'

View File

@@ -8,7 +8,7 @@ cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "24")
set(RELEASE_SERVICE_VERSION_MINOR "07")
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}")
@@ -84,7 +84,7 @@ if(ANDROID)
)
else()
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem Crash)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem)
find_package(KF6SyntaxHighlighting ${KF_MIN_VERSION} REQUIRED)
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
TYPE RUNTIME

View File

@@ -1,6 +1,6 @@
<!--
SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@kde.org>
SPDX-FileCopyrightText: 2020-2024 Tobias Fella <tobias.fella@kde.org>
SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
SPDX-License-Identifier: CC0-1.0
-->
@@ -16,18 +16,19 @@ A Qt/QML based Matrix client.
## Introduction
NeoChat is a client for [Matrix](https://matrix.org), the decentralized communication protocol for instant
messaging.
messaging. It is a fork of Spectral, using KDE frameworks, most notably [Kirigami](https://invent.kde.org/frameworks/kirigami)
to provide a convergent experience across multiple platforms.
NeoChat is based on KDE frameworks and as [libQuotient](https://github.com/quotient-im/libQuotient), a
NeoChat also make use of other KDE Frameworks as well as [libQuotient](https://github.com/quotient-im/libQuotient), a
Qt-based SDK for the [Matrix Protocol](https://spec.matrix.org/).
![Timeline](https://cdn.kde.org/screenshots/neochat/application.png)
## Features
NeoChat aims to be a fully featured application for the Matrix specification. As such most parts of the current specification are supported, with the notable exceptions
of VoIP, threads, and some aspects of End-to-End Encryption. 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.
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.
Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:
- Polls - MSC3381
@@ -38,9 +39,26 @@ Due to the nature of the Matrix specification development NeoChat also supports
Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat).
Nightly builds for linux and windows can be downloaded from [cdn.kde.org](https://cdn.kde.org/ci-builds/network/neochat/).
Nightly builds for android are available from [KDE's nightly F-Droid repository](https://community.kde.org/Android/F-Droid).
Nightly Flatpaks are available from [KDE's nightly Flatpak repository](https://userbase.kde.org/Tutorials/Flatpak).
In addition to the stable builds, unstable nightly builds are available for all platforms. These can be downloaded
from the [binary factory](https://binary-factory.kde.org/). There are unstable versions for the following platforms
in addition to stable ones:
- Android
- MacOS
- Windows
Additionally the nightly Flatpak version can be obtained from the nightly Flatpak repo using the following commands in your terminal:
```
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
flatpak remote-add --if-not-exists kdeapps --from https://distribute.kde.org/kdeapps.flatpakrepo
flatpak install kdeapps org.kde.neochat
```
The unstable Android version can also be obtained from the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid).
## Running
Just start the executable in your preferred way - either from the build directory or from the installed location.
## Building NeoChat
@@ -51,18 +69,14 @@ is primarily aimed at Linux development.
For Windows and Android [Craft](https://invent.kde.org/packaging/craft) is the primary choice. There are guides for setting up
development environments for [Windows](https://community.kde.org/Get_Involved/development/Windows) and [Android](https://develop.kde.org/docs/packaging/android/building_applications/).
## Running
Just start the executable in your preferred way - either from the build directory or from the installed location.
## Tests
Tests are in the repository under [autotests](autotests) and [appiumtests](appiumtests).
Tests are in the repository under [autotests](autotests) and should all pass for any contribution.
The project has CI setup to test new commits to the repository. All tests are expected to pass for a merge request to
be complete.
## Current build status
Current build status
![coverage](https://invent.kde.org/network/neochat/badges/master/pipeline.svg)
@@ -86,9 +100,9 @@ The best place to reach the maintainers is on the KDE Matrix instance in the Neo
## Acknowledgement
NeoChat utilizes [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
This program utilizes [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
NeoChat is a fork of [Spectral](https://gitlab.com/spectral-im/spectral/).
This program is a fork of [Spectral](https://gitlab.com/spectral-im/spectral/).
## License

View File

@@ -1,56 +0,0 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: MIT
# SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
import os
import subprocess
import sys
import unittest
import time
from appium import webdriver
from appium.options.common.base import AppiumOptions
from appium.webdriver.common.appiumby import AppiumBy
class CreateRoomTest(unittest.TestCase):
mockServerProcess: subprocess.Popen
@classmethod
def setUpClass(cls):
cls.mockServerProcess = subprocess.Popen([sys.executable, os.path.join(os.path.dirname(__file__), "login-server.py")])
options = AppiumOptions()
options.set_capability("app", "neochat --ignore-ssl-errors --test")
cls.driver = webdriver.Remote(command_executor='http://127.0.0.1:4723', options=options)
def setUp(self):
pass
def tearDown(self):
if not self._outcome.result.wasSuccessful():
self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id()))
@classmethod
def tearDownClass(self):
self.mockServerProcess.terminate()
self.driver.quit()
def test_create_room(self):
self.driver.find_element(by=AppiumBy.NAME, value="@user:localhost:1234").click()
self.driver.find_element(by=AppiumBy.NAME, value="Show Menu").click()
self.driver.find_element(by=AppiumBy.NAME, value="Create a Room").click()
self.driver.find_element(by=AppiumBy.NAME, value="Name:").send_keys("Super awesome room name")#
time.sleep(0.1) # without this, the second half of the text is sent to the topic field?!
self.driver.find_element(by=AppiumBy.NAME, value="Topic:").send_keys("There are not enough raccoons here")
time.sleep(0.1)
self.driver.find_element(by=AppiumBy.NAME, value="Create Room").click()
time.sleep(0.1)
self.driver.find_element(by=AppiumBy.NAME, value="Super awesome room name").click()
self.driver.find_element(by=AppiumBy.NAME, value="Show Room Information").click()
self.driver.find_element(by=AppiumBy.NAME, value="There are not enough raccoons here")
if __name__ == '__main__':
unittest.main()

View File

@@ -1,78 +0,0 @@
{
"next_batch": "batch1234",
"rooms": {
"join": {
"!newroom123321:localhost:1234": {
"state": {
"events": [
{
"type": "m.room.member",
"state_key": "@user:localhost:1234",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_0:localhost:1234",
"room_id": "!newroom123321:localhost:1234",
"content": {
"avatar_url": "",
"displayname": "A Display Name",
"membership": "join",
"reason": "Nothing"
},
"unsigned": {
"age": 1234
}
},
{
"type": "m.room.name",
"state_key": "",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_1:localhost:1234",
"room_id": "!newroom123321:localhost:1234",
"content": {
"name": "Super awesome room name"
},
"unsigned": {
"age": 1234
}
},
{
"type": "m.room.topic",
"state_key": "",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_2:localhost:1234",
"room_id": "!newroom123321:localhost:1234",
"content": {
"topic": "There are not enough raccoons here"
},
"unsigned": {
"age": 1234
}
}
]
},
"timeline": {
"events": [
{
"type": "m.room.message",
"sender": "@user:localhost:1234",
"origin_server_ts": 1432735824653,
"event_id": "$event_id_1234_1:localhost:1234",
"room_id": "!newroom123321:localhost:1234",
"content": {
"body": "This is a message",
"format": "org.matrix.custom.html",
"formatted_body": "<a href=\"https://matrix.to/#/@user:localhost:1234\">User</a>:",
"msgtype": "m.text"
},
"unsigned": {
"age": 1234
}
}
]
}
}
}
}
}

View File

@@ -6,8 +6,6 @@ from flask import Flask, request, abort
import os
app = Flask(__name__)
next_sync_payload = ""
@app.route("/_matrix/client/v3/login", methods=["GET"])
def login_get():
@@ -44,13 +42,8 @@ def load_json(name):
@app.route("/_matrix/client/r0/sync")
def sync():
global next_sync_payload
result = dict()
if len(next_sync_payload) > 0:
result = load_json(next_sync_payload)
next_sync_payload = ""
else:
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
result = load_json("sync_response_no_rooms") if ("login" in request.headers.get("Authorization")) else load_json("sync_response_rooms")
return result
@app.route("/.well-known/matrix/client")
@@ -72,18 +65,6 @@ def upload_keys():
reply = dict()
return reply
@app.route("/_matrix/client/v3/createRoom", methods=["POST"])
def create_room():
global next_sync_payload
data = request.get_json()
if data["name"] != "Super awesome room name" or data["topic"] != "There are not enough raccoons here":
return dict(), 400
response = dict()
response["room_id"] = "!newroom123321:localhost:1234"
next_sync_payload = "sync_response_new_room"
return response
if __name__ == "__main__":
app.run(ssl_context='adhoc', port=1234)

View File

@@ -39,10 +39,6 @@ class OpenUserDetailsTest(unittest.TestCase):
def test_open_sheet(self):
self.driver.find_element(by=AppiumBy.NAME, value="@user:localhost:1234").click()
try:
self.driver.find_element(by=AppiumBy.NAME, value="Expand Normal").click()
except:
pass
self.driver.find_element(by=AppiumBy.NAME, value="Empty room (!room_id_1234:localhost:1234)").click()
self.driver.find_element(by=AppiumBy.NAME, value="A Display Name").click()
self.driver.find_element(by=AppiumBy.NAME, value="Account Details")

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

@@ -130,7 +130,7 @@ void DelegateSizeHelperTest::equalBreakpoint_data()
}
/**
* We expect a default return except in the case where the two percentages are
* We expect a default return except in the case where the the two percentages are
* equal as that case can be calculated without dividing by zero.
*/
void DelegateSizeHelperTest::equalBreakpoint()

View File

@@ -56,7 +56,6 @@ private Q_SLOTS:
void genericBody();
void nullGenericBody();
void markdownBody();
void markdownBodyReply();
void subtitle();
void nullSubtitle();
void mediaInfo();
@@ -302,13 +301,6 @@ void EventHandlerTest::markdownBody()
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
}
void EventHandlerTest::markdownBodyReply()
{
EventHandler eventHandler(room, room->messageEvents().at(5).get());
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("reply"));
}
void EventHandlerTest::subtitle()
{
EventHandler eventHandler(room, room->messageEvents().at(0).get());

View File

@@ -6,11 +6,12 @@
#include "linkpreviewer.h"
#include "utils.h"
#include <Quotient/events/roommessageevent.h>
#include <Quotient/quotient_common.h>
#include <Quotient/syncdata.h>
#include "utils.h"
#include "testutils.h"
using namespace Quotient;
@@ -29,11 +30,10 @@ private Q_SLOTS:
void linkPreviewsMatch_data();
void linkPreviewsMatch();
void multipleLinkPreviewsMatch_data();
void multipleLinkPreviewsMatch();
void linkPreviewsReject_data();
void linkPreviewsReject();
void editedLink();
};
void LinkPreviewerTest::initTestCase()
@@ -44,59 +44,60 @@ void LinkPreviewerTest::initTestCase()
void LinkPreviewerTest::linkPreviewsMatch_data()
{
QTest::addColumn<QString>("inputString");
QTest::addColumn<QString>("eventSource");
QTest::addColumn<QUrl>("testOutputLink");
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QUrl("https://kde.org"_ls);
QTest::newRow("richHttpsLinkDescription") << QStringLiteral("<a href=\"https://kde.org\">https://kde.org</a>") << QUrl("https://kde.org"_ls);
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, inputString);
QFETCH(QString, eventSource);
QFETCH(QUrl, testOutputLink);
auto link = LinkPreviewer::linkPreviews(inputString)[0];
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(link, testOutputLink);
}
void LinkPreviewerTest::multipleLinkPreviewsMatch_data()
{
QTest::addColumn<QString>("inputString");
QTest::addColumn<QList<QUrl>>("testOutputLinks");
QTest::newRow("multipleHttps") << QStringLiteral("www.example.org https://kde.org") << QList{QUrl("www.example.org"_ls), QUrl("https://kde.org"_ls)};
QTest::newRow("multipleHttps1Invalid") << QStringLiteral("www.example.org mxc://example.org/SEsfnsuifSDFSSEF") << QList{QUrl("www.example.org"_ls)};
}
void LinkPreviewerTest::multipleLinkPreviewsMatch()
{
QFETCH(QString, inputString);
QFETCH(QList<QUrl>, testOutputLinks);
auto links = LinkPreviewer::linkPreviews(inputString);
QCOMPARE(links, testOutputLinks);
QCOMPARE(linkPreviewer.empty(), false);
QCOMPARE(linkPreviewer.url(), testOutputLink);
}
void LinkPreviewerTest::linkPreviewsReject_data()
{
QTest::addColumn<QString>("inputString");
QTest::addColumn<QString>("eventSource");
QTest::newRow("mxc") << QStringLiteral("mxc://example.org/SEsfnsuifSDFSSEF");
QTest::newRow("matrixTo") << QStringLiteral("https://matrix.to/#/@alice:example.org");
QTest::newRow("noSpace") << QStringLiteral("testhttps://kde.org");
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, inputString);
QFETCH(QString, eventSource);
auto links = LinkPreviewer::linkPreviews(inputString);
auto event = TestUtils::loadEventFromFile<RoomMessageEvent>(eventSource);
auto linkPreviewer = LinkPreviewer(room, event.get());
QCOMPARE(links.empty(), true);
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)

View File

@@ -84,7 +84,7 @@
<p xml:lang="eo">NeoChat estas babilej-apo, kiu ebligas al vi plene profiti de la Matrix-reto. Ĝi provizas al vi sekuran manieron sendi tekstmesaĝojn, filmetojn kaj sondosierojn al via familio, kolegoj kaj amikoj.</p>
<p xml:lang="es">NeoChat es una aplicación de chat que le permite aprovechar al máximo la red Matrix. Le proporciona un modo seguro de enviar mensajes de texto, vídeos y archivos de sonido a su familia, colegas y amigos.</p>
<p xml:lang="eu">NeoChat, Matrix sarearen abantaila guztiei probetsua ateratzeko aukera ematen dizun berriketa aplikaizo bat da. Zure familiari, kideei eta lagunei testu mezuak, bideoak eta audio fitxategiak era seguruan bidaltzeko aukera ematen dizu.</p>
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
<p xml:lang="fr">NeoChat est une application de discussions vous permettant de profiter pleinement du réseau Matrix. Elle vous offre un moyen sécurisé denvoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos ami(e)s.</p>
<p xml:lang="hu">A NeoChat egy olyan csevegőalkalmazás, amellyel teljes mértékben kihasználhatja a Matrix hálózatot. Biztonságos módot biztosít szöveges üzenetek, videók és hangfájlok küldéséhez családtagjainak, kollégáinak és barátainak.</p>
<p xml:lang="ia">NeoChat es un app de conversation que te permitte prender avantage plen del rete Matrix. Il te forni un modo secur de inviar messages de texto, videos e files audio a tui familia, collegas e amicos.</p>
<p xml:lang="it">NeoChat è un'applicazione di chat che ti consente di sfruttare appieno la rete Matrix. Ti fornisce un modo sicuro per inviare messaggi di testo, video e file audio a familiari, colleghi e amici.</p>
@@ -118,7 +118,6 @@
<p xml:lang="nn">NeoChat har som mål å støtta all funksjonalitet i Matrix-spesifikasjonen. Førebels er alt i den gjeldande stabile spesifikasjonen støtta, med unntak av VoIP, trådar og nokre delar av ende-til-kryptering. Det finst òg andre småting som ikkje er støtta, sidan Matrix-spesifikasjon er i stadig endring, men målet er altså støtte for alt.</p>
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
<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 Şifrelemenin 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>
@@ -146,7 +145,6 @@
<p xml:lang="nn">På grunn av måten Matrix-spesifikasjonen vert utvikla på, støttar NeoChat òg nokre uferdige funksjonar:</p>
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
<p xml:lang="sv">På grund av sättet Matrix-specifikationens utvecklas, stöder NeoChat också ett stor antal instabila funktioner. För närvarande är de:</p>
<p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் வித‍த்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p>
@@ -204,7 +202,6 @@
<li xml:lang="nn">Klistremerke-pakkar  MSC2545</li>
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
<li xml:lang="sl">Sticker Packs - MSC2545</li>
<li xml:lang="sv">Sticker Packs - MSC2545</li>
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
@@ -233,7 +230,6 @@
<li xml:lang="nn">Posisjonshendingar  MSC3488</li>
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
<li xml:lang="ru">События местоположения — MSC3488</li>
<li xml:lang="sl">Location Events - MSC3488</li>
<li xml:lang="sv">Location Events - MSC3488</li>
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
@@ -295,7 +291,6 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
<caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption>
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
@@ -355,7 +350,6 @@
<caption xml:lang="nn">Hovudvising med romliste, pratevindauge og rominformasjon</caption>
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
<caption xml:lang="sv">Huvudvy med rumslista, chatt, och rumsinformation</caption>
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
@@ -387,7 +381,6 @@
<caption xml:lang="nn">Innloggingsbilete</caption>
<caption xml:lang="pl">Ekran logowania</caption>
<caption xml:lang="pt">Ecrã de autenticação</caption>
<caption xml:lang="ru">Окно входа</caption>
<caption xml:lang="sl">Prijavni zaslon</caption>
<caption xml:lang="sv">Inloggningsfönster</caption>
<caption xml:lang="ta">நுழைவுத் திரை</caption>
@@ -401,8 +394,6 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="24.05.0" date="2024-05-23"/>
<release version="24.02.2" date="2024-04-11"/>
<release version="24.02.1" date="2024-03-21"/>
<release version="24.02.0" date="2024-02-28">
<url>https://kde.org/announcements/megarelease/6/#neochat</url>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -20,6 +20,8 @@ add_library(neochat STATIC
models/customemojimodel.h
clipboard.cpp
clipboard.h
matriximageprovider.cpp
matriximageprovider.h
models/messageeventmodel.cpp
models/messageeventmodel.h
models/messagefiltermodel.cpp
@@ -172,15 +174,6 @@ add_library(neochat STATIC
sharehandler.h
models/roomtreeitem.cpp
models/roomtreeitem.h
foreigntypes.h
models/threepidmodel.cpp
models/threepidmodel.h
threepidaddhelper.cpp
threepidaddhelper.h
jobs/neochatadd3pidjob.cpp
jobs/neochatadd3pidjob.h
identityserverhelper.cpp
identityserverhelper.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -190,7 +183,7 @@ set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat
QML_FILES
qml/Main.qml
qml/main.qml
qml/AccountMenu.qml
qml/ExploreComponent.qml
qml/ExploreComponentMobile.qml
@@ -207,14 +200,35 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/ExplorerDelegate.qml
qml/InviteUserPage.qml
qml/ImageEditorPage.qml
qml/WelcomePage.qml
qml/NeochatMaximizeComponent.qml
qml/FancyEffectsContainer.qml
qml/TypingPane.qml
qml/QuickSwitcher.qml
qml/HoverActions.qml
qml/ChatBar.qml
qml/AttachmentPane.qml
qml/ReplyPane.qml
qml/CompletionMenu.qml
qml/PieProgressBar.qml
qml/QuickFormatBar.qml
qml/EmojiPicker.qml
qml/LoginStep.qml
qml/Login.qml
qml/Homeserver.qml
qml/Username.qml
qml/RegisterPassword.qml
qml/Captcha.qml
qml/Terms.qml
qml/Email.qml
qml/Password.qml
qml/LoginRegister.qml
qml/Loading.qml
qml/LoginMethod.qml
qml/Sso.qml
qml/UserDetailDialog.qml
qml/CreateRoomDialog.qml
qml/EmojiDialog.qml
qml/OpenFileDialog.qml
qml/KeyVerificationDialog.qml
qml/ConfirmLogoutDialog.qml
@@ -234,6 +248,9 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/ConfirmEncryptionDialog.qml
qml/RemoveSheet.qml
qml/BanSheet.qml
qml/EmojiTonesPicker.qml
qml/EmojiDelegate.qml
qml/EmojiGrid.qml
qml/RoomSearchPage.qml
qml/LocationChooser.qml
qml/TimelineView.qml
@@ -257,6 +274,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/SelectParentDialog.qml
qml/QrCodeMaximizeComponent.qml
qml/SelectSpacesDialog.qml
qml/AttachDialog.qml
qml/NotificationsView.qml
qml/SearchPage.qml
qml/ServerComboBox.qml
@@ -273,17 +291,14 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
qml/AccountSwitchDialog.qml
qml/ConfirmLeaveDialog.qml
qml/CodeMaximizeComponent.qml
qml/EditStateDialog.qml
qml/ConsentDialog.qml
qml/AskDirectChatConfirmation.qml
qml/HoverLinkIndicator.qml
RESOURCES
qml/confetti.png
qml/glowdot.png
)
add_subdirectory(settings)
add_subdirectory(timeline)
add_subdirectory(devtools)
add_subdirectory(login)
add_subdirectory(chatbar)
if(UNIX)
qt_target_qml_sources(neochat QML_FILES qml/ShareAction.qml)
@@ -366,18 +381,16 @@ endif()
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
target_compile_definitions(neochat PUBLIC -DHAVE_X11=1)
target_compile_definitions(neochat PUBLIC -DHAVE_X11)
target_sources(neochat PRIVATE runner.cpp)
if (TARGET KUnifiedPush)
target_sources(neochat PRIVATE fakerunner.cpp)
endif()
else()
target_compile_definitions(neochat PUBLIC -DHAVE_X11=0)
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin loginplugin chatbarplugin)
target_link_libraries(neochat PRIVATE settingsplugin timelineplugin devtoolsplugin)
target_link_libraries(neochat PUBLIC
Qt::Core
Qt::Quick
@@ -401,10 +414,6 @@ target_link_libraries(neochat PUBLIC
QCoro::Network
)
if (TARGET KF6::Crash)
target_link_libraries(neochat PUBLIC KF6::Crash)
endif()
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
if(NEOCHAT_FLATPAK)

View File

@@ -1,19 +0,0 @@
# SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(chatbar STATIC)
qt_add_qml_module(chatbar
URI org.kde.neochat.chatbar
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/chatbar
QML_FILES
AttachDialog.qml
ChatBar.qml
CompletionMenu.qml
EmojiDelegate.qml
EmojiGrid.qml
ReplyPane.qml
PieProgressBar.qml
EmojiPicker.qml
EmojiDialog.qml
EmojiTonesPicker.qml
)

View File

@@ -91,7 +91,7 @@ class ChatDocumentHandler : public QObject
Q_PROPERTY(CompletionModel *completionModel READ completionModel CONSTANT)
/**
* @brief The current room that the text document is being handled for.
* @brief The current room that the the text document is being handled for.
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)

View File

@@ -9,20 +9,28 @@
#include <KLocalizedString>
#include <QGuiApplication>
#include <QNetworkProxy>
#include <QQuickTextDocument>
#include <QQuickWindow>
#include <QStandardPaths>
#include <QStringBuilder>
#include <QTimer>
#include <signal.h>
#include <Quotient/accountregistry.h>
#include <Quotient/connection.h>
#include <Quotient/csapi/logout.h>
#include <Quotient/csapi/notifications.h>
#include <Quotient/eventstats.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#include "roommanager.h"
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
#include "trayicon.h"
@@ -38,10 +46,6 @@
#endif
#endif
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
bool testMode = false;
using namespace Quotient;
@@ -153,7 +157,7 @@ void Controller::addConnection(NeoChatConnection *c)
});
connect(c, &NeoChatConnection::loggedOut, this, [this, c] {
if (accounts().count() > 1) {
// Only set the connection if the account being logged out is currently active
// Only set the connection if the the account being logged out is currently active
if (c == activeConnection()) {
setActiveConnection(dynamic_cast<NeoChatConnection *>(accounts().accounts()[0]));
}
@@ -186,7 +190,7 @@ void Controller::invokeLogin()
m_accountsLoading += accountId;
Q_EMIT accountsLoadingChanged();
if (!account.homeserver().isEmpty()) {
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account.userId());
auto accessTokenLoadingJob = loadAccessTokenFromKeyChain(account);
connect(accessTokenLoadingJob, &QKeychain::Job::finished, this, [accountId, this, accessTokenLoadingJob](QKeychain::Job *) {
AccountSettings account{accountId};
QString accessToken;
@@ -214,11 +218,11 @@ void Controller::invokeLogin()
}
}
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QString &userId)
QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const AccountSettings &account)
{
qDebug() << "Reading access token from the keychain for" << userId;
qDebug() << "Reading access token from the keychain for" << account.userId();
auto job = new QKeychain::ReadPasswordJob(qAppName(), this);
job->setKey(userId);
job->setKey(account.userId());
// Handling of errors
connect(job, &QKeychain::Job::finished, this, [this, job]() {
@@ -249,12 +253,12 @@ QKeychain::ReadPasswordJob *Controller::loadAccessTokenFromKeyChain(const QStrin
return job;
}
bool Controller::saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken)
bool Controller::saveAccessTokenToKeyChain(const AccountSettings &account, const QByteArray &accessToken)
{
qDebug() << "Save the access token to the keychain for " << userId;
qDebug() << "Save the access token to the keychain for " << account.userId();
QKeychain::WritePasswordJob job(qAppName());
job.setAutoDelete(false);
job.setKey(userId);
job.setKey(account.userId());
job.setBinaryData(accessToken);
QEventLoop loop;
QKeychain::WritePasswordJob::connect(&job, &QKeychain::Job::finished, &loop, &QEventLoop::quit);

View File

@@ -5,9 +5,15 @@
#include <QObject>
#include <QQmlEngine>
#include <QQuickItem>
#include "neochatconnection.h"
#include <Quotient/accountregistry.h>
#include <Quotient/settings.h>
#ifdef HAVE_KUNIFIEDPUSH
#include <kunifiedpush/connector.h>
#endif
class TrayIcon;
class QQuickTextDocument;
@@ -76,7 +82,7 @@ public:
/**
* @brief Save an access token to the keychain for the given account.
*/
bool saveAccessTokenToKeyChain(const QString &userId, const QByteArray &accessToken);
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
[[nodiscard]] bool supportSystemTray() const;
@@ -104,14 +110,14 @@ private:
QPointer<NeoChatConnection> m_connection;
TrayIcon *m_trayIcon = nullptr;
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const QString &account);
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
void loadSettings();
void saveSettings() const;
Quotient::AccountRegistry m_accountRegistry;
QStringList m_accountsLoading;
QMap<QString, QPointer<NeoChatConnection>> m_connectionsLoading;
QMap<QString, QPointer<Quotient::Connection>> m_connectionsLoading;
QString m_endpoint;
private Q_SLOTS:

View File

@@ -23,7 +23,7 @@ ColumnLayout {
model: root.connection.accountDataEventTypes
delegate: FormCard.FormButtonDelegate {
text: modelData
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet.qml'), {
sourceText: root.connection.accountDataJsonString(modelData)
}, {
title: i18nc("@title:window", "Event Source"),

View File

@@ -8,7 +8,6 @@ qt_add_qml_module(devtools
QML_FILES
DevtoolsPage.qml
AccountData.qml
DebugOptions.qml
FeatureFlagPage.qml
RoomData.qml
ServerData.qml

View File

@@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
import QtQuick
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
FormCard.FormCardPage {
id: root
FormCard.FormCard {
Layout.topMargin: Kirigami.Units.largeSpacing
FormCard.FormCheckDelegate {
text: i18nc("@option:check", "Show hidden events in the timeline")
checked: Config.showAllEvents
onToggled: Config.showAllEvents = checked
}
FormCard.FormCheckDelegate {
id: roomAccountDataVisibleCheck
text: i18nc("@option:check Enable the matrix 'threads' feature", "Always allow device verification")
description: i18n("Allow the user to start a verification session with devices that were already verified")
checked: Config.alwaysVerifyDevice
onToggled: Config.alwaysVerifyDevice = checked
}
FormCard.FormCheckDelegate {
text: i18nc("@option:check", "Show focus in window header")
checked: Config.windowTitleFocus
onToggled: {
Config.windowTitleFocus = checked;
Config.save();
}
}
}
}

View File

@@ -26,17 +26,12 @@ FormCard.FormCardPage {
readonly property real tabWidth: tabBar.width / tabBar.count
QQC2.TabButton {
text: i18nc("@title:tab", "Debug Options")
text: qsTr("Room Data")
implicitWidth: tabBar.tabWidth
}
QQC2.TabButton {
text: i18nc("@title:tab", "Room Data")
implicitWidth: tabBar.tabWidth
}
QQC2.TabButton {
text: i18nc("@title:tab", "Server Info")
text: qsTr("Server Info")
implicitWidth: tabBar.tabWidth
}
@@ -57,7 +52,6 @@ FormCard.FormCardPage {
currentIndex: tabBar.currentIndex
DebugOptions {}
RoomData {
room: root.room
connection: root.connection

View File

@@ -7,7 +7,7 @@ import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
import org.kde.neochat.config
FormCard.FormCardPage {
id: root
@@ -28,14 +28,5 @@ FormCard.FormCardPage {
onToggled: Config.secretBackup = checked
}
FormCard.FormCheckDelegate {
text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs")
checked: Config.phone3PId
onToggled: {
Config.phone3PId = checked
Config.save();
}
}
}
}

View File

@@ -25,10 +25,9 @@ ColumnLayout {
text: i18n("Room")
textRole: "escapedDisplayName"
valueRole: "roomId"
displayText: RoomManager.roomListModel.data(RoomManager.roomListModel.index(currentIndex, 0), RoomListModel.EscapedDisplayNameRole)
displayText: RoomManager.roomListModel.data(RoomManager.roomListModel.index(currentIndex, 0), RoomListModel.DisplayNameRole)
model: RoomManager.roomListModel
currentIndex: 0
displayMode: FormCard.FormComboBoxDelegate.Page
Component.onCompleted: currentIndex = RoomManager.roomListModel.rowForRoom(root.room)
onCurrentValueChanged: root.room = RoomManager.roomListModel.roomByAliasOrId(roomComboBox.currentValue)
}
@@ -47,7 +46,7 @@ ColumnLayout {
model: root.room.accountDataEventTypes
delegate: FormCard.FormButtonDelegate {
text: modelData
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet.qml'), {
sourceText: root.room.roomAcountDataJson(text)
}, {
title: i18n("Event Source"),
@@ -75,9 +74,14 @@ ColumnLayout {
description: i18ncp("'Event' being some JSON data, not something physically happening.", "%1 event of this type", "%1 events of this type", model.eventCount)
onClicked: {
if (model.eventCount === 1) {
openEventSource(model.type, model.stateKey);
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet.qml'), {
sourceText: stateModel.stateEventJson(stateModel.index(model.index, 0))
}, {
title: i18n("Event Source"),
width: Kirigami.Units.gridUnit * 25
})
} else {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.devtools', 'StateKeys'), {
pageStack.pushDialogLayer(stateKeysComponent, {
room: root.room,
eventType: model.type
}, {
@@ -87,17 +91,9 @@ ColumnLayout {
}
}
}
}
function openEventSource(type: string, stateKey: string): void {
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateModel,
allowEdit: true,
room: root.room,
type: type,
stateKey: stateKey,
}, {
title: i18n("Event Source"),
width: Kirigami.Units.gridUnit * 25
});
Component {
id: stateKeysComponent
StateKeys {}
}
}
}

View File

@@ -31,21 +31,13 @@ FormCard.FormCardPage {
delegate: FormCard.FormButtonDelegate {
text: model.stateKey
onClicked: openEventSource(model.stateKey)
onClicked: applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet.qml'), {
sourceText: stateKeysModel.stateEventJson(stateKeysModel.index(model.index, 0))
}, {
title: i18nc("@title:window", "Event Source"),
width: Kirigami.Units.gridUnit * 25
})
}
}
}
function openEventSource(stateKey: string): void {
applicationWindow().pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'MessageSourceSheet'), {
model: stateKeysModel,
allowEdit: true,
room: root.room,
type: root.eventType,
stateKey: stateKey
}, {
title: i18nc("@title:window", "Event Source"),
width: Kirigami.Units.gridUnit * 25
});
}
}

View File

@@ -21,6 +21,7 @@ public:
* @brief Defines the room list categories a room can be assigned.
*/
enum Types {
Search = 0, /**< So we can show a search delegate if needed, e.g. collapsed mode. */
Invited, /**< The user has been invited to the room. */
Favorite, /**< The room is set as a favourite. */
Direct, /**< The room is a direct chat. */
@@ -67,6 +68,8 @@ public:
return i18n("Low priority");
case NeoChatRoomType::Space:
return i18n("Spaces");
case NeoChatRoomType::Search:
return i18n("Search");
default:
return {};
}
@@ -86,6 +89,8 @@ public:
return QStringLiteral("object-order-lower");
case NeoChatRoomType::Space:
return QStringLiteral("group");
case NeoChatRoomType::Search:
return QStringLiteral("search");
default:
return QStringLiteral("tools-report-bug");
}

View File

@@ -293,10 +293,7 @@ QString EventHandler::getMarkdownBody() const
}
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
QString plainBody = roomMessageEvent->plainBody();
plainBody.remove(TextRegex::removeReply);
return plainBody;
return roomMessageEvent->plainBody();
}
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
@@ -1041,11 +1038,7 @@ QString EventHandler::getReadMarkersString() const
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
for (const auto &userId : userIds) {
auto user = m_room->user(userId);
auto displayName = user->displayname(m_room);
if (displayName.isEmpty()) {
displayName = userId;
}
readMarkersString += displayName + i18nc("list separator", ", ");
readMarkersString += user->displayname(m_room) + i18nc("list separator", ", ");
}
readMarkersString.chop(2);
return readMarkersString;

View File

@@ -74,9 +74,6 @@ void ImagePackEventContent::fillJson(QJsonObject *o) const
}
imageJson["usage"_ls] = usageJson;
}
if (image.info.has_value()) {
imageJson["info"_ls] = Quotient::EventContent::toInfoJson(*image.info);
}
imagesJson[image.shortcode] = imageJson;
}
(*o)["images"_ls] = imagesJson;

View File

@@ -89,4 +89,6 @@ public:
QUO_EVENT(ImagePackEvent, "im.ponies.room_emotes")
using KeyedStateEventBase::KeyedStateEventBase;
};
REGISTER_EVENT_TYPE(ImagePackEvent)
}

View File

@@ -40,4 +40,5 @@ public:
*/
QJsonArray allow() const;
};
REGISTER_EVENT_TYPE(JoinRulesEvent)
}

View File

@@ -33,4 +33,4 @@ FakeRunner::FakeRunner()
qDBusRegisterMetaType<RemoteImage>();
}
#include "moc_fakerunner.cpp"
#include "moc_fakerunner.cpp"

View File

@@ -1,56 +0,0 @@
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: LGPL-2.0-or-later
#pragma once
#include <QQmlEngine>
#include <Quotient/accountregistry.h>
#include <Quotient/keyverificationsession.h>
#if __has_include("Quotient/e2ee/sssshandler.h")
#include <Quotient/e2ee/sssshandler.h>
#endif
#include "controller.h"
#include "neochatconfig.h"
struct ForeignConfig {
Q_GADGET
QML_FOREIGN(NeoChatConfig)
QML_NAMED_ELEMENT(Config)
QML_SINGLETON
public:
static NeoChatConfig *create(QQmlEngine *, QJSEngine *)
{
QQmlEngine::setObjectOwnership(NeoChatConfig::self(), QQmlEngine::CppOwnership);
return NeoChatConfig::self();
}
};
struct ForeignAccountRegistry {
Q_GADGET
QML_FOREIGN(Quotient::AccountRegistry)
QML_NAMED_ELEMENT(AccountRegistry)
QML_SINGLETON
public:
static Quotient::AccountRegistry *create(QQmlEngine *, QJSEngine *)
{
QQmlEngine::setObjectOwnership(&Controller::instance().accounts(), QQmlEngine::CppOwnership);
return &Controller::instance().accounts();
}
};
struct ForeignKeyVerificationSession {
Q_GADGET
QML_FOREIGN(Quotient::KeyVerificationSession)
QML_NAMED_ELEMENT(KeyVerificationSession)
QML_UNCREATABLE("")
};
#if __has_include("Quotient/e2ee/sssshandler.h")
struct ForeignSSSSHandler {
Q_GADGET
QML_FOREIGN(Quotient::SSSSHandler)
QML_NAMED_ELEMENT(SSSSHandler)
};
#endif

View File

@@ -1,154 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "identityserverhelper.h"
#include <QNetworkReply>
#include <KLocalizedString>
#include <Quotient/networkaccessmanager.h>
#include "neochatconnection.h"
IdentityServerHelper::IdentityServerHelper(QObject *parent)
: QObject(parent)
{
}
NeoChatConnection *IdentityServerHelper::connection() const
{
return m_connection;
}
void IdentityServerHelper::setConnection(NeoChatConnection *connection)
{
if (m_connection == connection) {
return;
}
if (m_connection != nullptr) {
m_connection->disconnect(this);
}
m_connection = connection;
Q_EMIT connectionChanged();
Q_EMIT currentServerChanged();
connect(m_connection, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
if (type == QLatin1String("m.identity_server")) {
Q_EMIT currentServerChanged();
}
});
}
QString IdentityServerHelper::currentServer() const
{
if (m_connection == nullptr) {
return {};
}
if (!m_connection->hasAccountData(QLatin1String("m.identity_server"))) {
return i18nc("@info", "No identity server configured");
}
const auto url = m_connection->accountData(QLatin1String("m.identity_server"))->contentPart<QUrl>(QLatin1String("base_url"));
if (!url.isEmpty()) {
return url.toString();
}
return i18nc("@info", "No identity server configured");
}
bool IdentityServerHelper::hasCurrentServer() const
{
if (m_connection == nullptr && !m_connection->hasAccountData(QLatin1String("m.identity_server"))) {
return false;
}
const auto url = m_connection->accountData(QLatin1String("m.identity_server"))->contentPart<QUrl>(QLatin1String("base_url"));
if (!url.isEmpty()) {
return true;
}
return false;
}
QString IdentityServerHelper::url() const
{
return m_url;
}
void IdentityServerHelper::setUrl(const QString &url)
{
if (url == m_url) {
return;
}
m_url = url;
Q_EMIT urlChanged();
checkUrl();
}
IdentityServerHelper::IdServerStatus IdentityServerHelper::status() const
{
return m_status;
}
void IdentityServerHelper::checkUrl()
{
if (m_idServerCheckRequest != nullptr) {
m_idServerCheckRequest->abort();
m_idServerCheckRequest.clear();
}
if (m_url == currentServer()) {
m_status = Match;
Q_EMIT statusChanged();
return;
}
if (m_url.isEmpty()) {
m_status = Valid;
Q_EMIT statusChanged();
return;
}
const auto requestUrl = QUrl(m_url + QStringLiteral("/_matrix/identity/v2"));
if (!(requestUrl.scheme() == QStringLiteral("https") || requestUrl.scheme() == QStringLiteral("http"))) {
m_status = Invalid;
Q_EMIT statusChanged();
return;
}
QNetworkRequest request(requestUrl);
m_idServerCheckRequest = Quotient::NetworkAccessManager::instance()->get(request);
connect(m_idServerCheckRequest, &QNetworkReply::finished, this, [this]() {
if (m_idServerCheckRequest->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt() == 200) {
m_status = Valid;
Q_EMIT statusChanged();
} else {
m_status = Invalid;
Q_EMIT statusChanged();
}
});
}
void IdentityServerHelper::setIdentityServer()
{
if (m_url == currentServer()) {
return;
}
m_connection->setAccountData(QLatin1String("m.identity_server"), {{QLatin1String("base_url"), m_url}});
m_status = Ready;
Q_EMIT statusChanged();
}
void IdentityServerHelper::clearIdentityServer()
{
if (currentServer().isEmpty()) {
return;
}
m_connection->setAccountData(QLatin1String("m.identity_server"), {{QLatin1String("base_url"), QString()}});
m_status = Ready;
Q_EMIT statusChanged();
}

View File

@@ -1,103 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QObject>
#include <QQmlEngine>
#include <Quotient/jobs/basejob.h>
class NeoChatConnection;
/**
* @class IdentityServerHelper
*
* This class is designed to help the process of setting an identity server for the account.
* It will manage the various stages of verification and authentication.
*/
class IdentityServerHelper : public QObject
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The connection to add a 3PID to.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
/**
* @brief The current identity server.
*/
Q_PROPERTY(QString currentServer READ currentServer NOTIFY currentServerChanged)
/**
* @brief Whether an identity server is currently configured.
*/
Q_PROPERTY(bool hasCurrentServer READ hasCurrentServer NOTIFY currentServerChanged)
/**
* @brief The URL for the desired server.
*/
Q_PROPERTY(QString url READ url WRITE setUrl NOTIFY urlChanged)
/**
* @brief The current status.
*/
Q_PROPERTY(IdServerStatus status READ status NOTIFY statusChanged)
public:
/**
* @brief The current status for adding an identity server
*/
enum IdServerStatus {
Ready, /**< The process is ready to start. I.e. there is no ongoing attempt to set a new server. */
Valid, /**< The server URL is valid. */
Invalid, /**< The server URL is invalid. */
Match, /**< The server URL is the one that is already configured. */
Other, /**< An unknown problem occurred. */
};
Q_ENUM(IdServerStatus)
explicit IdentityServerHelper(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] QString currentServer() const;
[[nodiscard]] bool hasCurrentServer() const;
[[nodiscard]] QString url() const;
void setUrl(const QString &url);
[[nodiscard]] IdServerStatus status() const;
/**
* @brief Set the current URL as the user's identity server.
*
* Will do nothing if the URL isn't a valid identity server.
*/
Q_INVOKABLE void setIdentityServer();
/**
* @brief Clear the user's identity server.
*/
Q_INVOKABLE void clearIdentityServer();
Q_SIGNALS:
void connectionChanged();
void currentServerChanged();
void urlChanged();
void statusChanged();
private:
QPointer<NeoChatConnection> m_connection;
IdServerStatus m_status = Ready;
QString m_url;
QPointer<QNetworkReply> m_idServerCheckRequest;
void checkUrl();
};

View File

@@ -1,16 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "neochatadd3pidjob.h"
using namespace Quotient;
NeochatAdd3PIdJob::NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Omittable<QJsonObject> &auth)
: BaseJob(HttpVerb::Post, QStringLiteral("Add3PIDJob"), makePath("/_matrix/client/v3", "/account/3pid/add"))
{
QJsonObject _dataJson;
addParam<IfNotEmpty>(_dataJson, QStringLiteral("auth"), auth);
addParam<>(_dataJson, QStringLiteral("client_secret"), clientSecret);
addParam<>(_dataJson, QStringLiteral("sid"), sid);
setRequestData({_dataJson});
}

View File

@@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <Quotient/jobs/basejob.h>
#include <Quotient/omittable.h>
class NeochatAdd3PIdJob : public Quotient::BaseJob
{
public:
explicit NeochatAdd3PIdJob(const QString &clientSecret, const QString &sid, const Quotient::Omittable<QJsonObject> &auth = Quotient::none);
};

View File

@@ -8,22 +8,34 @@
#include <Quotient/events/roommessageevent.h>
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "utils.h"
using namespace Quotient;
LinkPreviewer::LinkPreviewer(const QUrl &url, QObject *parent)
LinkPreviewer::LinkPreviewer(const NeoChatRoom *room, const Quotient::RoomMessageEvent *event, QObject *parent)
: QObject(parent)
, m_currentRoom(room)
, m_event(event)
, m_loaded(false)
, m_url(url)
, m_url(linkPreview(event))
{
Q_ASSERT(dynamic_cast<Connection *>(this->parent()));
connect(this, &LinkPreviewer::urlChanged, this, &LinkPreviewer::emptyChanged);
connect(NeoChatConfig::self(), &NeoChatConfig::ShowLinkPreviewChanged, this, &LinkPreviewer::loadUrlPreview);
loadUrlPreview();
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);
}
bool LinkPreviewer::loaded() const
@@ -53,14 +65,14 @@ QUrl LinkPreviewer::url() const
void LinkPreviewer::loadUrlPreview()
{
if (!m_currentRoom || !NeoChatConfig::showLinkPreview() || !m_currentRoom->urlPreviewEnabled()) {
return;
}
if (m_url.scheme() == QStringLiteral("https")) {
m_loaded = false;
Q_EMIT loadedChanged();
auto conn = dynamic_cast<Connection *>(this->parent());
if (conn == nullptr) {
return;
}
auto conn = m_currentRoom->connection();
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url);
connect(job, &BaseJob::success, this, [this, job, conn]() {
@@ -89,23 +101,38 @@ bool LinkPreviewer::empty() const
return m_url.isEmpty();
}
QList<QUrl> LinkPreviewer::linkPreviews(QString string)
QUrl LinkPreviewer::linkPreview(const Quotient::RoomMessageEvent *event)
{
auto data = string.remove(TextRegex::removeRichReply);
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);
QList<QUrl> links;
while (linksMatch.hasNext()) {
auto link = linksMatch.next().captured();
if (!link.contains(QStringLiteral("matrix.to")) && !links.contains(QUrl(link))) {
links += QUrl(link);
if (!link.contains(QStringLiteral("matrix.to"))) {
return QUrl(link);
}
}
return links;
return {};
}
bool LinkPreviewer::hasPreviewableLinks(const QString &string)
bool LinkPreviewer::hasPreviewableLinks(const Quotient::RoomMessageEvent *event)
{
return !linkPreviews(string).isEmpty();
return !linkPreview(event).isEmpty();
}
#include "moc_linkpreviewer.cpp"

View File

@@ -7,6 +7,11 @@
#include <QQmlEngine>
#include <QUrl>
namespace Quotient
{
class RoomMessageEvent;
}
class NeoChatRoom;
/**
@@ -48,15 +53,14 @@ class LinkPreviewer : public QObject
Q_PROPERTY(QUrl imageSource READ imageSource NOTIFY imageSourceChanged)
/**
* @brief Whether there is a link to preview.
* @brief Whether the there is a link to preview.
*
* A linkPreviwer is empty if the URL is empty.
*/
Q_PROPERTY(bool empty READ empty NOTIFY emptyChanged)
public:
LinkPreviewer() = default;
explicit LinkPreviewer(const QUrl &url, QObject *parent = nullptr);
explicit LinkPreviewer(const NeoChatRoom *room = nullptr, const Quotient::RoomMessageEvent *event = nullptr, QObject *parent = nullptr);
[[nodiscard]] QUrl url() const;
[[nodiscard]] bool loaded() const;
@@ -66,21 +70,16 @@ public:
[[nodiscard]] bool empty() const;
/**
* @brief Whether the given string has at least 1 pre-viewable link.
* @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 QString &string);
/**
* @brief Return previewable links from the given string.
*
* 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 QList<QUrl> linkPreviews(QString string);
static bool hasPreviewableLinks(const Quotient::RoomMessageEvent *event);
private:
const NeoChatRoom *m_currentRoom;
const Quotient::RoomMessageEvent *m_event;
bool m_loaded;
QString m_title = QString();
QString m_description = QString();
@@ -89,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

@@ -4,8 +4,8 @@
#include "login.h"
#include <Quotient/accountregistry.h>
#include <Quotient/connection.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "controller.h"
@@ -54,7 +54,7 @@ void LoginHelper::init()
m_connection = new NeoChatConnection();
}
m_connection->resolveServer(m_matrixId);
connectSingleShot(m_connection.get(), &Connection::loginFlowsChanged, this, [this]() {
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
setHomeserverReachable(true);
m_testing = false;
Q_EMIT testingChanged();
@@ -73,7 +73,7 @@ void LoginHelper::init()
account.setHomeserver(m_connection->homeserver());
account.setDeviceId(m_connection->deviceId());
account.setDeviceName(m_deviceName);
if (!Controller::instance().saveAccessTokenToKeyChain(account.userId(), m_connection->accessToken())) {
if (!Controller::instance().saveAccessTokenToKeyChain(account, m_connection->accessToken())) {
qWarning() << "Couldn't save access token";
}
account.sync();
@@ -100,7 +100,7 @@ void LoginHelper::init()
Q_EMIT Controller::instance().errorOccured(i18n("Network Error"), std::move(error));
});
connectSingleShot(m_connection.get(), &Connection::syncDone, this, [this]() {
connectSingleShot(m_connection, &Connection::syncDone, this, [this]() {
Q_EMIT loaded();
});
}
@@ -182,7 +182,7 @@ QUrl LoginHelper::ssoUrl() const
void LoginHelper::loginWithSso()
{
m_connection->resolveServer(m_matrixId);
connectSingleShot(m_connection.get(), &Connection::loginFlowsChanged, this, [this]() {
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
SsoSession *session = m_connection->prepareForSso(m_deviceName);
m_ssoUrl = session->ssoUrl();
Q_EMIT ssoUrlChanged();

View File

@@ -146,7 +146,7 @@ private:
QString m_deviceName;
bool m_supportsSso = false;
bool m_supportsPassword = false;
QPointer<NeoChatConnection> m_connection;
NeoChatConnection *m_connection = nullptr;
QUrl m_ssoUrl;
bool m_testing = false;
bool m_isLoggingIn = false;

View File

@@ -1,23 +0,0 @@
# SPDX-FileCopyrightText: 2024 James Graham <james.h.graham@protonmail.com>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(login STATIC)
qt_add_qml_module(login
URI org.kde.neochat.login
OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/src/org/kde/neochat/login
QML_FILES
WelcomePage.qml
LoginStep.qml
Captcha.qml
Email.qml
Homeserver.qml
Loading.qml
Login.qml
LoginMethod.qml
LoginRegister.qml
Password.qml
RegisterPassword.qml
Sso.qml
Terms.qml
Username.qml
)

View File

@@ -5,7 +5,6 @@
#include <QIcon>
#include <QNetworkDiskCache>
#include <QNetworkProxyFactory>
#include <QNetworkReply>
#include <QObject>
#include <QQmlApplicationEngine>
#include <QQmlContext>
@@ -31,26 +30,27 @@
#ifdef HAVE_WINDOWSYSTEM
#include <KWindowSystem>
#endif
#if __has_include("KCrash")
#include <KCrash>
#endif
#include <KLocalizedContext>
#include <KLocalizedString>
#include "neochat-version.h"
#include <Quotient/accountregistry.h>
#if __has_include("Quotient/e2ee/sssshandler.h")
#include <Quotient/e2ee/sssshandler.h>
#endif
#include <Quotient/keyverificationsession.h>
#include <Quotient/networkaccessmanager.h>
#include "blurhashimageprovider.h"
#include "colorschemer.h"
#include "controller.h"
#include "logger.h"
#include "matriximageprovider.h"
#include "neochatconfig.h"
#include "roommanager.h"
#include "sharehandler.h"
#include "windowcontroller.h"
#include "sharehandler.h"
#ifdef HAVE_RUNNER
#include "runner.h"
@@ -167,10 +167,6 @@ int main(int argc, char *argv[])
KAboutData::setApplicationData(about);
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat")));
#if __has_include("KCrash")
KCrash::initialize();
#endif
initLogging();
Connection::setEncryptionDefault(true);
@@ -235,10 +231,15 @@ int main(int argc, char *argv[])
Q_IMPORT_QML_PLUGIN(org_kde_neochat_settingsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_timelinePlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_devtoolsPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_loginPlugin)
Q_IMPORT_QML_PLUGIN(org_kde_neochat_chatbarPlugin)
qml_register_types_org_kde_neochat();
qmlRegisterSingletonInstance("org.kde.neochat.config", 1, 0, "Config", NeoChatConfig::self());
qmlRegisterSingletonInstance("org.kde.neochat.accounts", 1, 0, "AccountRegistry", &Controller::instance().accounts());
qmlRegisterUncreatableType<KeyVerificationSession>("com.github.quotient_im.libquotient", 1, 0, "KeyVerificationSession", {});
#if __has_include("Quotient/e2ee/sssshandler.h")
qmlRegisterType<SSSSHandler>("com.github.quotient_im.libquotient", 1, 0, "SSSSHandler");
#endif
QQmlApplicationEngine engine;
@@ -286,9 +287,10 @@ int main(int argc, char *argv[])
ShareHandler::instance().setText(parser.value(shareOption));
}
engine.addImageProvider(QLatin1String("mxc"), MatrixImageProvider::create(&engine, &engine));
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
engine.loadFromModule("org.kde.neochat", "Main");
engine.load(QUrl(QStringLiteral("qrc:/qt/qml/org/kde/neochat/qml/main.qml")));
if (engine.rootObjects().isEmpty()) {
return -1;
}

View File

@@ -14,6 +14,8 @@
#include "neochatconnection.h"
#include <Quotient/connection.h>
using namespace Quotient;
ThumbnailResponse::ThumbnailResponse(QString id, QSize size, NeoChatConnection *connection)

View File

@@ -35,7 +35,7 @@ private:
QSize requestedSize;
const QString localFile;
Quotient::MediaThumbnailJob *job = nullptr;
QPointer<NeoChatConnection> m_connection;
NeoChatConnection *m_connection;
QImage image;
QString errorStr;
@@ -75,6 +75,6 @@ public:
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
private:
QPointer<NeoChatConnection> m_connection;
NeoChatConnection *m_connection = nullptr;
MatrixImageProvider() = default;
};

View File

@@ -7,5 +7,3 @@ void MediaManager::startPlayback()
{
Q_EMIT playbackStarted();
}
#include "moc_mediamanager.cpp"

View File

@@ -3,15 +3,9 @@
#include "accountemoticonmodel.h"
#include <QImage>
#include <QMimeDatabase>
#include <Quotient/csapi/content-repo.h>
#include <Quotient/events/eventcontent.h>
#include <qcoro/qcorosignal.h>
#include "neochatconnection.h"
using namespace Quotient;
AccountEmoticonModel::AccountEmoticonModel(QObject *parent)
@@ -37,7 +31,7 @@ QVariant AccountEmoticonModel::data(const QModelIndex &index, int role) const
const auto &row = index.row();
const auto &image = m_images->images[row];
if (role == UrlRole) {
return m_connection->makeMediaUrl(image.url).toString();
return m_connection->makeMediaUrl(image.url);
}
if (role == BodyRole) {
if (image.body) {
@@ -79,12 +73,12 @@ QHash<int, QByteArray> AccountEmoticonModel::roleNames() const
};
}
NeoChatConnection *AccountEmoticonModel::connection() const
Connection *AccountEmoticonModel::connection() const
{
return m_connection;
}
void AccountEmoticonModel::setConnection(NeoChatConnection *connection)
void AccountEmoticonModel::setConnection(Connection *connection)
{
if (m_connection) {
disconnect(m_connection, nullptr, this, nullptr);
@@ -163,24 +157,12 @@ void AccountEmoticonModel::setEmoticonImage(int index, const QUrl &source)
QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl source)
{
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
#if Quotient_VERSION_MINOR > 8
co_await qCoro(job.get(), &BaseJob::finished);
#else
co_await qCoro(job, &BaseJob::finished);
#endif
if (job->error() != BaseJob::NoError) {
co_return;
}
m_images->images[index].url = job->contentUri();
auto mime = QMimeDatabase().mimeTypeForUrl(source);
source.setScheme("file"_ls);
QFileInfo fileInfo(source.isLocalFile() ? source.toLocalFile() : source.toString());
EventContent::ImageInfo info;
if (mime.name().startsWith("image/"_ls)) {
QImage image(source.toLocalFile());
info = EventContent::ImageInfo(source, fileInfo.size(), mime, image.size(), fileInfo.fileName());
}
m_images->images[index].info = info;
m_images->images[index].info = none;
QJsonObject data;
m_images->fillJson(&data);
m_connection->setAccountData("im.ponies.user_emotes"_ls, data);
@@ -189,29 +171,15 @@ QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl sourc
QCoro::Task<void> AccountEmoticonModel::doAddEmoticon(QUrl source, QString shortcode, QString description, QString type)
{
auto job = m_connection->uploadFile(source.isLocalFile() ? source.toLocalFile() : source.toString());
#if Quotient_VERSION_MINOR > 8
co_await qCoro(job.get(), &BaseJob::finished);
#else
co_await qCoro(job, &BaseJob::finished);
#endif
if (job->error() != BaseJob::NoError) {
co_return;
}
auto mime = QMimeDatabase().mimeTypeForUrl(source);
source.setScheme("file"_ls);
QFileInfo fileInfo(source.isLocalFile() ? source.toLocalFile() : source.toString());
EventContent::ImageInfo info;
if (mime.name().startsWith("image/"_ls)) {
QImage image(source.toLocalFile());
info = EventContent::ImageInfo(source, fileInfo.size(), mime, image.size(), fileInfo.fileName());
}
m_images->images.append(ImagePackEventContent::ImagePackImage{
shortcode,
job->contentUri(),
description,
info,
none,
QStringList{type},
});
QJsonObject data;

View File

@@ -12,7 +12,7 @@
#include <QPointer>
#include <QQmlEngine>
class NeoChatConnection;
#include <Quotient/connection.h>
/**
* @class AccountEmoticonModel
@@ -29,7 +29,7 @@ class AccountEmoticonModel : public QAbstractListModel
/**
* @brief The connection to get emoticons from.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
public:
enum Roles {
@@ -63,8 +63,8 @@ public:
*/
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] Quotient::Connection *connection() const;
void setConnection(Quotient::Connection *connection);
/**
* @brief Deletes the emoticon at the given index.
@@ -96,7 +96,7 @@ Q_SIGNALS:
private:
std::optional<Quotient::ImagePackEventContent> m_images;
QPointer<NeoChatConnection> m_connection;
QPointer<Quotient::Connection> m_connection;
QCoro::Task<void> doSetEmoticonImage(int index, QUrl source);
QCoro::Task<void> doAddEmoticon(QUrl source, QString shortcode, QString description, QString type);

View File

@@ -4,7 +4,6 @@
#include "actionsmodel.h"
#include "chatbarcache.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "roommanager.h"
#include <Quotient/events/roommemberevent.h>
@@ -261,7 +260,7 @@ QList<ActionsModel::Action> actions{
return QString();
}
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
auto connection = dynamic_cast<NeoChatConnection *>(room->connection());
auto connection = room->connection();
const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
if (parts.length() >= 2) {
RoomManager::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});

View File

@@ -153,13 +153,13 @@ QVariant CustomEmojiModel::data(const QModelIndex &idx, int role) const
switch (Roles(role)) {
case Roles::ModelData:
return QVariant::fromValue(Emoji(m_connection->makeMediaUrl(QUrl(data.url)).toString(), data.name, true));
return QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + data.url.mid(6), data.name, true));
case Roles::Name:
case Roles::DisplayRole:
case Roles::ReplacedTextRole:
return data.name;
case Roles::ImageURL:
return m_connection->makeMediaUrl(QUrl(data.url));
return QUrl(QStringLiteral("image://mxc/") + data.url.mid(6));
case Roles::MxcUrl:
return m_connection->makeMediaUrl(QUrl(data.url));
default:
@@ -205,7 +205,7 @@ QVariantList CustomEmojiModel::filterModel(const QString &filter)
if (!emoji.name.contains(filter, Qt::CaseInsensitive))
continue;
results << QVariant::fromValue(Emoji(m_connection->makeMediaUrl(QUrl(emoji.url)).toString(), emoji.name, true));
results << QVariant::fromValue(Emoji(QStringLiteral("image://mxc/") + emoji.url.mid(6), emoji.name, true));
}
return results;
}

View File

@@ -110,7 +110,7 @@ Q_SIGNALS:
private:
explicit CustomEmojiModel(QObject *parent = nullptr);
QList<CustomEmoji> m_emojis;
QPointer<NeoChatConnection> m_connection;
NeoChatConnection *m_connection = nullptr;
void fetchEmojis();
};

View File

@@ -5,8 +5,7 @@
#include "customemojimodel.h"
#include <QRegularExpression>
class NeoChatConnection;
#include <connection.h>
struct CustomEmoji {
QString name; // with :semicolons:
@@ -15,6 +14,6 @@ struct CustomEmoji {
};
struct CustomEmojiModel::Private {
QPointer<NeoChatConnection> connection;
Quotient::Connection *conn = nullptr;
QList<CustomEmoji> emojies;
};

View File

@@ -11,10 +11,9 @@
#include <KLocalizedString>
#include <Quotient/csapi/device_management.h>
#include <Quotient/connection.h>
#include <Quotient/user.h>
#include "neochatconnection.h"
using namespace Quotient;
DevicesModel::DevicesModel(QObject *parent)
@@ -128,12 +127,8 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
authData["type"_ls] = "m.login.password"_ls;
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, m_connection->user()->id()}};
authData["identifier"_ls] = identifier;
auto innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
#if Quotient_VERSION_MINOR > 8
connect(innerJob.get(), &BaseJob::success, this, onSuccess);
#else
auto *innerJob = m_connection->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
connect(innerJob, &BaseJob::success, this, onSuccess);
#endif
} else {
onSuccess();
}
@@ -157,12 +152,12 @@ void DevicesModel::setName(const QString &deviceId, const QString &name)
});
}
NeoChatConnection *DevicesModel::connection() const
Connection *DevicesModel::connection() const
{
return m_connection;
}
void DevicesModel::setConnection(NeoChatConnection *connection)
void DevicesModel::setConnection(Connection *connection)
{
if (m_connection) {
disconnect(m_connection, nullptr, this, nullptr);

View File

@@ -9,7 +9,10 @@
#include <Quotient/csapi/definitions/client_device.h>
class NeoChatConnection;
namespace Quotient
{
class Connection;
}
/**
* @class DevicesModel
@@ -28,7 +31,7 @@ class DevicesModel : public QAbstractListModel
/**
* @brief The current connection that the model is getting its devices from.
*/
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged REQUIRED)
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged REQUIRED)
public:
/**
@@ -85,8 +88,9 @@ public:
explicit DevicesModel(QObject *parent = nullptr);
[[nodiscard]] NeoChatConnection *connection() const;
void setConnection(NeoChatConnection *connection);
[[nodiscard]] Quotient::Connection *connection() const;
void setConnection(Quotient::Connection *connection);
Q_SIGNALS:
void connectionChanged();
@@ -95,5 +99,5 @@ Q_SIGNALS:
private:
void fetchDevices();
QList<Quotient::Device> m_devices;
QPointer<NeoChatConnection> m_connection;
QPointer<Quotient::Connection> m_connection;
};

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