Compare commits
1 Commits
work/tobia
...
work/carl/
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5222f3b221 |
@@ -41,7 +41,6 @@
|
||||
{
|
||||
"name": "olm",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DOLM_TESTS=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
@@ -103,7 +102,6 @@
|
||||
{
|
||||
"name": "libQuotient",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DBUILD_TESTING=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
@@ -119,7 +117,6 @@
|
||||
{
|
||||
"name": "cmark",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DCMARK_TESTS=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "git",
|
||||
@@ -136,7 +133,6 @@
|
||||
{
|
||||
"name": "qcoro",
|
||||
"buildsystem": "cmake-ninja",
|
||||
"config-opts": [ "-DQCORO_BUILD_EXAMPLES=OFF", "-DBUILD_TESTING=OFF" ],
|
||||
"sources": [
|
||||
{
|
||||
"type": "archive",
|
||||
|
||||
@@ -14,7 +14,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
|
||||
set(KF_MIN_VERSION "5.105.0")
|
||||
set(KF_MIN_VERSION "5.91.0")
|
||||
set(QT_MIN_VERSION "5.15.2")
|
||||
if (ANDROID)
|
||||
set(QT_MIN_VERSION "5.15.8")
|
||||
@@ -39,7 +39,6 @@ include(KDECompilerSettings NO_POLICY_SCOPE)
|
||||
include(ECMAddAppIcon)
|
||||
include(KDEGitCommitHooks)
|
||||
include(ECMCheckOutboundLicense)
|
||||
include(ECMQtDeclareLoggingCategory)
|
||||
if (NOT ANDROID)
|
||||
include(KDEClangFormat)
|
||||
endif()
|
||||
@@ -94,12 +93,8 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
find_package(KF${QT_MAJOR_VERSION}DBusAddons ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
|
||||
if(QT_MAJOR_VERSION STREQUAL "6" AND NOT ANDROID AND NOT WIN32)
|
||||
set(QUOTIENT_SUFFIX "Qt6")
|
||||
endif()
|
||||
|
||||
find_package(Quotient${QUOTIENT_SUFFIX} 0.6)
|
||||
set_package_properties(Quotient${QUOTIENT_SUFFIX} PROPERTIES
|
||||
find_package(Quotient 0.6)
|
||||
set_package_properties(Quotient PROPERTIES
|
||||
TYPE REQUIRED
|
||||
DESCRIPTION "Qt wrapper around Matrix API"
|
||||
URL "https://github.com/quotient-im/libQuotient/"
|
||||
@@ -136,7 +131,7 @@ set_package_properties(KF${QT_MAJOR_VERSION}DocTools PROPERTIES DESCRIPTION
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
|
||||
if(NOT Quotient${QUOTIENT_SUFFIX}_VERSION_MINOR GREATER 6)
|
||||
if(NOT Quotient_VERSION_MINOR GREATER 6)
|
||||
cmake_policy(SET CMP0063 OLD)
|
||||
endif()
|
||||
|
||||
@@ -155,7 +150,7 @@ install(FILES org.kde.neochat.tray.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/h
|
||||
add_definitions(-DQT_NO_FOREACH)
|
||||
|
||||
add_subdirectory(src)
|
||||
if (BUILD_TESTING AND Quotient${QUOTIENT_SUFFIX}_VERSION_MINOR GREATER 6)
|
||||
if (BUILD_TESTING AND Quotient_VERSION_MINOR GREATER 6)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
|
||||
add_subdirectory(autotests)
|
||||
endif()
|
||||
|
||||
100
README.md
100
README.md
@@ -1,51 +1,24 @@
|
||||
<!--
|
||||
SPDX-FileCopyrightText: 2020-2021 Carl Schwan <carlschwan@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
|
||||
-->
|
||||
|
||||
# NeoChat
|
||||
|
||||
A Qt/QML based Matrix client.
|
||||
NeoChat is a client for Matrix, the decentralized communication protocol for instant
|
||||
messaging. It is a fork of Spectral, using KDE frameworks, most notably Kirigami,
|
||||
KConfig and KI18n.
|
||||
|
||||
<a href='https://matrix.org'><img src='https://matrix.org/docs/projects/images/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
|
||||
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
|
||||
|
||||
## Introduction
|
||||
|
||||
NeoChat is a client for [Matrix](https://matrix.org), the decentralized communication protocol for instant
|
||||
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 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/).
|
||||
|
||||

|
||||
|
||||
## Features
|
||||
|
||||
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
|
||||
- Sticker Packs - MSC2545
|
||||
- Location Events - MSC3488
|
||||
|
||||
## Get it
|
||||
|
||||
Details where to find stable releases for NeoChat can be found on its [homepage](https://apps.kde.org/neochat).
|
||||
A stable release [is available](https://apps.kde.org/neochat) for download for Linux distributions.
|
||||
|
||||
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:
|
||||
Along with the stable release, a Flatpak version is available for the nightly
|
||||
version:
|
||||
|
||||
```
|
||||
flatpak remote-add --if-not-exists flathub https://flathub.org/repo/flathub.flatpakrepo
|
||||
@@ -53,53 +26,42 @@ flatpak remote-add --if-not-exists kdeapps --from https://distribute.kde.org/kde
|
||||
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).
|
||||
A nightly build is also available for Android in the [KDE nightly F-Droid repo](https://community.kde.org/Android/FDroid)
|
||||
and can also directly be downloaded from the [binary factory](https://binary-factory.kde.org/view/Android/job/NeoChat_Nightly_android-arm64/).
|
||||
|
||||
## Running
|
||||
Nightly builds for [Windows](https://binary-factory.kde.org/job/NeoChat_Nightly_win64/), [MacOS](https://binary-factory.kde.org/job/NeoChat_Nightly_macos/) and [AppImages](https://binary-factory.kde.org/job/NeoChat_Nightly_appimage/) can also be downloaded from the [binary factory](https://binary-factory.kde.org/search/?q=neochat).
|
||||
|
||||
Just start the executable in your preferred way - either from the build directory or from the installed location.
|
||||

|
||||
|
||||
## Building NeoChat
|
||||
## Features
|
||||
|
||||
The best way to build KDE apps during development is to use `kdesrc-build`. The full instructions for this can be found on
|
||||
the KDE community website's get involved section under [development](https://community.kde.org/Get_Involved/development). This
|
||||
is primarily aimed at Linux development.
|
||||
* Sending messages
|
||||
* Sending files from clipboard and filesystem
|
||||
* Reply to message (right-click on a message to access menu)
|
||||
* Start a private chat (but not encrypted)
|
||||
* Show notifications, for the moment there is only a global switch
|
||||
to disable it. We plan to implement the configuration part of the
|
||||
specification soon.
|
||||
* Autocompletion of usernames in chat
|
||||
* Emoji picker
|
||||
* Basic room setting page
|
||||
* Send and accept invitations
|
||||
* /rainbow <message> (very important)
|
||||
* /me <message>
|
||||
|
||||
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/).
|
||||
|
||||
## Tests
|
||||
|
||||
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
|
||||
|
||||

|
||||
|
||||
Currently the number of tests is limited, but growing. If anyone wants to help improve this, those
|
||||
contributions would be especially welcome.
|
||||
|
||||
## Contributing
|
||||
|
||||
As is the case throughout the KDE ecosystem contributions are welcome from all. The code base is managed in the
|
||||
[NeoChat repository](https://invent.kde.org/network/neochat) of the KDE Gitlab instance.
|
||||
|
||||
- [Code of Conduct](https://kde.org/code-of-conduct)
|
||||
- [Report a Bug](https://bugs.kde.org/enter_bug.cgi?format=guided&product=neochat)
|
||||
- [Feature Request](https://community.kde.org/Infrastructure/GitLab#Submitting_a_merge_request)
|
||||
- [Create a Merge Request](https://community.kde.org/Infrastructure/GitLab#Submitting_a_merge_request)
|
||||
- [Translation](https://community.kde.org/Get_Involved/translation)
|
||||
NeoChat is still missing a few features to become a full-featured
|
||||
Matrix client (most notably encryption support and video chat support).
|
||||
We welcome contributions in this direction.
|
||||
|
||||
## Contact
|
||||
|
||||
The best place to reach the maintainers is on the KDE Matrix instance in the NeoChat channel, [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org).
|
||||
You can reach the maintainers at [#neochat:kde.org](https://matrix.to/#/#neochat:kde.org), if you are already on Matrix.
|
||||
Development happens in http://invent.kde.org/network/neochat (not in GitHub).
|
||||
|
||||
## Acknowledgement
|
||||
|
||||
This program utilizes [libQuotient](https://github.com/quotient-im/libQuotient/) as its Matrix SDK.
|
||||
This program utilizes [libQuotient](https://github.com/quotient-im/libQuotient/)
|
||||
library and some C++ models from [Quaternion](https://github.com/quotient-im/Quaternion/).
|
||||
|
||||
This program is a fork of [Spectral](https://gitlab.com/spectral-im/spectral/).
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ enable_testing()
|
||||
|
||||
ecm_add_test(
|
||||
neochatroomtest.cpp
|
||||
LINK_LIBRARIES neochat Qt::Test
|
||||
LINK_LIBRARIES neochat Qt::Test Quotient
|
||||
TEST_NAME neochatroomtest
|
||||
)
|
||||
|
||||
|
||||
@@ -144,5 +144,5 @@ void NeoChatRoomTest::eventTest()
|
||||
QCOMPARE(room->timelineSize(), 1);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(NeoChatRoomTest)
|
||||
QTEST_MAIN(NeoChatRoomTest)
|
||||
#include "neochatroomtest.moc"
|
||||
|
||||
@@ -63,12 +63,6 @@ private Q_SLOTS:
|
||||
void receiveRichEmote();
|
||||
void receiveRichEdited_data();
|
||||
void receiveRichEdited();
|
||||
void receiveLineSeparator();
|
||||
|
||||
void linkPreviewsMatch_data();
|
||||
void linkPreviewsMatch();
|
||||
void linkPreviewsReject_data();
|
||||
void linkPreviewsReject();
|
||||
};
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
@@ -593,60 +587,5 @@ void TextHandlerTest::receiveRichEdited()
|
||||
QCOMPARE(testTextHandler.handleRecieveRichText(Qt::RichText, room, room->messageEvents().at(2).get()), testOutputString);
|
||||
}
|
||||
|
||||
void TextHandlerTest::receiveLineSeparator()
|
||||
{
|
||||
auto text = QStringLiteral("foo\u2028bar");
|
||||
TextHandler textHandler;
|
||||
textHandler.setData(text);
|
||||
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")});
|
||||
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org")});
|
||||
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org")});
|
||||
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
|
||||
<< QList<QUrl>({
|
||||
QUrl("https://kde.org"),
|
||||
QUrl("www.example.org"),
|
||||
});
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
QTEST_GUILESS_MAIN(TextHandlerTest)
|
||||
QTEST_MAIN(TextHandlerTest)
|
||||
#include "texthandlertest.moc"
|
||||
|
||||
@@ -47,8 +47,8 @@
|
||||
<summary>A client for matrix, the decentralized communication protocol</summary>
|
||||
<summary xml:lang="ar">عميل لماتركس، ميفاق الاتصال اللامركزي</summary>
|
||||
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
|
||||
<summary xml:lang="ca">Un client de Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client de Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca">Un client per al Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client per a Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
|
||||
<summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
|
||||
<summary xml:lang="el">Ένας πελάτης για το Matrix, το αποκεντρωμένο πρωτόκολλο επικοινωνίας</summary>
|
||||
@@ -79,85 +79,99 @@
|
||||
<summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary>
|
||||
<summary xml:lang="zh-CN">分布式通讯协议 Matrix 的客户端</summary>
|
||||
<description>
|
||||
<p>NeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami
|
||||
to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="ar">نيوتشات هو عميل ماتركس Matrix، (ميفاق الاتصال اللامركزي للمراسلة الفورية). يتيح لك نيوتشات إرسال رسائل نصية ومقاطع فيديو وملفات صوتية إلى عائلتك وزملائك وأصدقائك. يستخدم أطر عمل كيدي وأبرزها Kirigami لتوفير تجربة متقاربة عبر منصات متعددة.</p>
|
||||
<p xml:lang="ca">El NeoChat és un client de Matrix, el protocol descentralitzat de comunicacions de missatgeria instantània. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics. Fa servir els Frameworks de KDE i, sobretot, el Kirigami per a proporcionar una experiència convergent a través de diverses plataformes.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat és un client de Matrix, el protocol descentralitzat de comunicacions de missatgeria instantània. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics. Utilitza els Frameworks de KDE i, sobretot, Kirigami per a proporcionar una experiència convergent a través de diverses plataformes.</p>
|
||||
<p xml:lang="es">NeoChat es un cliente para Matrix, el protocolo de comunicaciones descentralizado para mensajería instantánea. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos. Usa la infraestructura de KDE y, en particular, Kirigami para proporcionar una experiencia convergente en muchas plataformas.</p>
|
||||
<p>NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="ar">نيوتشات هو عميل ماتركس Matrix. يتيح لك إرسال رسائل نصية ومقاطع فيديو وملفات صوتية إلى عائلتك وزملائك وأصدقائك باستخدام بروتوكول ماتركس</p>
|
||||
<p xml:lang="az">NeoChat Mtrix müştərisidir. O, Matrix protokolundan istifadə edərək, ailənizə, dostlarınıza, iş yoldaşlarınıza mətn, səsli və görüntülü ismarıclar göndərməyə imkan verir.</p>
|
||||
<p xml:lang="ca">El NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics usant el protocol Matrix.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat és un client de Matrix. Permet enviar missatges de text, fitxers de vídeo i d'àudio a la família, col·legues i amics utilitzant el protocol Matrix.</p>
|
||||
<p xml:lang="de">NeoChat ist ein Matrix-Client. Er ermöglicht Ihnen das Senden von Textnachrichten, Videos und Audiodateien an Ihre Familie, Kollegen und Freunde unter Verwendung des Matrix-Protokolls.</p>
|
||||
<p xml:lang="el">Το NeoChat είναι μια εφαρμογή του Matrix. Σας επιτρέπει να στέλνετε μηνύματα κειμένου, βίντεο και ήχο στην οικογένειά σας, σε συναδέλφους και φίλους με το πρωτόκολλο Matrix.</p>
|
||||
<p xml:lang="en-GB">NeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.</p>
|
||||
<p xml:lang="es">NeoChat es un cliente para Matrix. Le permite enviar mensajes de texto, vídeos y archivos de sonido a su familia, compañeros de trabajo y amigos usando el protocolo Matrix.</p>
|
||||
<p xml:lang="eu">NeoChat Matrix bezero bat da. Familiari, lankideei eta lagunei testu-mezuak, bideoak eta audio-fitxategiak bidaltzeko aukera ematen du, Matrix protokoloa erabiliz.</p>
|
||||
<p xml:lang="fi">NeoChat on Matrix-asiakas. Sillä voi lähettää perheelle, tuttaville ja kavereille tekstiviestejä sekä video- ja äänitiedostoja Matrix-yhteyskäytännöllä.</p>
|
||||
<p xml:lang="fr">NeoChat est un client Matrix. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis en utilisant le protocole Matrix.</p>
|
||||
<p xml:lang="hu">A NeoChat egy Matrix kliens. Szöveges üzeneteket, videókat ésaudio fájlokat küldhet családjának, kollégáinak és barátainak a Matrix protokoll használatával.</p>
|
||||
<p xml:lang="ia">NeoChat es un cliente de Matrix. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante le protocollo de Matrix.</p>
|
||||
<p xml:lang="id">NeoChat adalah sebuah klien Matrix. Memungkinkan Anda untuk mengirim pesan teks, file video dan audio ke keluarga, kolega dan teman Anda menggunakan protokol Matrix.</p>
|
||||
<p xml:lang="ie">NeoChat es un cliente de Matrix. On posse usar it por inviar missages textual, video- e audiofiles a su familie, colegos e amics med li protocol Matrix.</p>
|
||||
<p xml:lang="it">NeoChat è un client Matrix. Ti consente di inviare messaggi di testo, file video e audio a familiari, colleghi e amici utilizzando il protocollo Matrix.</p>
|
||||
<p xml:lang="ka">NeoChat არის Matrix კლიენტი. ის საშუალებას გაძლევთ გაგზავნოთ ტექსტური შეტყობინებები, ვიდეოები და აუდიო ფაილები თქვენს ოჯახს, კოლეგებსა და მეგობრებს მატრიქსის პროტოკოლის გამოყენებით.</p>
|
||||
<p xml:lang="nl">NeoChat is een client voor Matrix, het gedecentraliseerde communicatieprotocol voor instant messages. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden. Het gebruik KDE frameworks en het meest opmerkelijk Kirigami om een convergente ervaring te leveren op meerdere platforms.</p>
|
||||
<p xml:lang="pl">NeoChat jest programem do Matriksa, protokołu rozproszonego porozumiewania się w czasie rzeczywistym. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół. Używa szkieletów KDE i głównie Kirigami, aby zapewnić spójne wrażenia na wielu platformach</p>
|
||||
<p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix. Usa as plataformas do KDE, e principalmente o Kirigami, para oferecer uma experiência convergente entre várias plataformas.</p>
|
||||
<p xml:lang="sl">Neochat je odjemalec za Matrix, decentralizirani komunikacijski protokol za takojšnje sporočanje. Omogoča vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek svoji družini, sodelavcem in prijateljem. Uporablja okvire ogrodje KDE frameworks in predvsem Kirigami za zagotavljanje konvergentne izkušnje na več platformah.</p>
|
||||
<p xml:lang="tr">NeoChat, anlık iletileşme için merkezi olmayan iletişim protokolü olan Matrix için bir istemcidir. Ailenize, iş arkadaşlarınıza ve arkadaşlarınıza metin iletiler, videolar ve ses dosyaları göndermenize olanak tanır. Birden çok platformda yakınsak bir deneyim sağlamak için KDE Frameworks ve en önemlilerinden Kirigami'yi kullanır.</p>
|
||||
<p xml:lang="uk">NeoChat — клієнт Matrix, децентралізованого протоколу спілкування для миттєвого обміну повідомленнями. За його допомогою ви можете надсилати текстові повідомлення, відео та звукові файли вашій родин, колегами та друзям. У програмі використано бібліотеки KDE, зокрема Kirigami, для надання однорідного середовища на декількох програмних та апаратних платформах.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami to provide a convergent experience across multiple platforms.xx</p>
|
||||
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
|
||||
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. على هذا النحو يتم دعم كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP والخيوط وبعض جوانب التشفير من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار ، ولكن يبقى الهدف توفير الدعم النهائي للمواصفات بأكملها.</p>
|
||||
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="es">NeoChat pretende ser una aplicación con todas las funciones para la especificación de Matrix. Como tal, admite todo en la especificación estable actual, con las notables excepciones de VoIP, subprocesos y algunas funciones de cifrado de extremo a extremo. Existen algunas omisiones menos importantes debido al hecho de que la especificación de Matrix está en constante evolución, pero el objetivo sigue siendo brindar compatibilidad final con toda la especificación.</p>
|
||||
<p xml:lang="ka">NeoChat-ი მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარგდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
|
||||
<p xml:lang="nl">NeoChat richt zich op het volledig bieden van alle mogelijkheden van de Matrix-specificatie. Alles in de huidige stabiele specificatie met merkbare uitzondering van VoIP, gekoppelde discussies en sommige aspecten van eind-tot-eind versleuteling worden ondersteund. Er zijn een paar andere kleinere omissies vanwege het feit dat de Matrix specificatie constant evolueert maar het doel blijft het eventueel bieden van ondersteuning van de gehele specificatie.</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="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="tr">NeoChat, Matrix spesifikasyonu 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>
|
||||
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
|
||||
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
|
||||
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يدعم نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
|
||||
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="es">Debido a la naturaleza del desarrollo de la especificación de Matrix, NeoChat también permite numerosas funciones no estables, como:</p>
|
||||
<p xml:lang="ka">Matrix-ის სპეციფიკაციის განვითარების ბუნების გამო NeoChat-ს ასევე აქვს უამრავი არასტაბილური ფუნქციაც. ახლა ისინია:</p>
|
||||
<p xml:lang="nl">Vanwege de aard van de ontwikkeling van de Matrix specificatie ondersteunt NeoChat ook talloze onstabiele mogelijkheden. Dit zijn nu:</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="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
|
||||
<p xml:lang="tr">NeoChat, Matrix belirtim geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
|
||||
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
|
||||
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
|
||||
<ul>
|
||||
<li>Polls - MSC3381</li>
|
||||
<li xml:lang="ar">التصويت - MSC3381</li>
|
||||
<li xml:lang="ca">Enquestes - MSC3381</li>
|
||||
<li xml:lang="ca-valencia">Enquestes - MSC3381</li>
|
||||
<li xml:lang="es">Encuestas - MSC3381</li>
|
||||
<li xml:lang="fr">Sondages - MSC3381</li>
|
||||
<li xml:lang="ka">Polls - MSC3381</li>
|
||||
<li xml:lang="nl">Polls - MSC3381</li>
|
||||
<li xml:lang="pt">Inquéritos - MSC3381</li>
|
||||
<li xml:lang="sl">Polls - MSC3381</li>
|
||||
<li xml:lang="tr">Anketler - MSC3381</li>
|
||||
<li xml:lang="uk">Опитування - MSC3381</li>
|
||||
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
|
||||
<li>Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
|
||||
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="ca-valencia">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="es">Paquetes de pegatinas - MSC2545</li>
|
||||
<li xml:lang="ka">სტიკერების პაკეტები - MSC2545</li>
|
||||
<li xml:lang="nl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
|
||||
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="tr">Yapışkan Paketleri - MSC2545</li>
|
||||
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
|
||||
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
|
||||
<li>Location Events - MSC3488</li>
|
||||
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
|
||||
<li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="ca-valencia">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="es">Eventos de ubicación - MSC3488</li>
|
||||
<li xml:lang="ka">მდებარეობის მოვლენები - MSC3488</li>
|
||||
<li xml:lang="nl">Locatie gebeurtenissen - MSC3488</li>
|
||||
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
|
||||
<li xml:lang="sl">Location Events - MSC3488</li>
|
||||
<li xml:lang="tr">Konum Etkinlikleri - MSC3488</li>
|
||||
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
|
||||
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
|
||||
</ul>
|
||||
<p xml:lang="ko">NeoChat은 Matrix 클라이언트입니다. Matrix 프로토콜을 사용하여 가족, 동료, 친구에게 텍스트 메시지, 동영상, 오디오 파일을 전송할 수 있습니다.</p>
|
||||
<p xml:lang="nl">NeoChat is een Matrix-client. Het biedt u het verzenden van tekstberichten, video's en geluidsbestanden naar uw familie, collega's en vrienden met het Matrix-protocol.</p>
|
||||
<p xml:lang="pl">NeoChat jest programem do Matrisa. Umożliwia wysyłanie wiadomości tekstowych, filmów oraz dźwięku do twojej rodziny, znajomych oraz przyjaciół poprzez protokół Matriksa.</p>
|
||||
<p xml:lang="pt">O NeoChat é um cliente do Matrix. O mesmo permite-lhe enviar mensagens de texto, ficheiros de vídeo e áudio para a sua família, colegas e amigos com o protocolo Matrix.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat é um cliente Matrix. Ele permite a você enviar mensagens de texto, arquivos de vídeo e áudio para seus familiares, colegas e amigos usando o protocolo Matrix.</p>
|
||||
<p xml:lang="ru">NeoChat — это клиент, поддерживающий работу с протоколом Matrix. Он позволяет отправлять текстовые сообщения, видео и аудиофайлы.</p>
|
||||
<p xml:lang="sk">NeoChat je Matrix klient. Umožňuje vám posielať textové správy, videá a zvukové súbory rodine, kolegom a priateľom pomocou protokolu Matrix.</p>
|
||||
<p xml:lang="sl">NeoChat je odjemalec Matrixa. Dovoljuje vam pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, kolegom in prijateljem z uporabo protokola Matrix.</p>
|
||||
<p xml:lang="sv">NeoChat är en Matrix-klient. Den låter dig skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner med användning av Matrix-protokollet.</p>
|
||||
<p xml:lang="tr">NeoChat, bir Matrix istemcisidir. Matrix protokolünü kullanarak ailenize, iş arkadaşlarınıza, arkadaşlarınıza ve başkalarına metin iletileri, video ve ses dosyaların göndermenize olanak verir.</p>
|
||||
<p xml:lang="uk">NeoChat — клієнт мережі обміну повідомленнями Matrix. За допомогою цієї програми ви зможете надсилати текстові повідомлення, відео та звукові файли вашій родині, колегам та друзям за допомогою протоколу Matrix.</p>
|
||||
<p xml:lang="x-test">xxNeoChat is a Matrix client. It allows you to send text messages, videos and audio files to your family, colleagues and friends using the Matrix protocol.xx</p>
|
||||
<p xml:lang="zh-CN">NeoChat 是一个 Matrix 客户端。 它允许您使用 Matrix 协议向您的家人、同事和朋友发送文本消息、视频和音频文件。</p>
|
||||
<p>Matrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="ar">ماتريكس هو بروتوكول اتصال لامركزي ، يعيد المستخدم إلى السيطرة. يطبق نيوتشات حاليًا جزءًا كبيرًا من الميفاق باستثناء الدردشات المشفرة ودردشة الفيديو.</p>
|
||||
<p xml:lang="az">Matrix, istifadəçini nəzarətdə saxlayan, mərkəzləşməmişi rabitə protokoludur. NeoChat, söhbətin və video əlaqəsinin şifrələnməsindən başqa bir çox protokolları həyata keçirə bilir.</p>
|
||||
<p xml:lang="ca">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment el NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="ca-valencia">Matrix és un protocol de comunicacions descentralitzat, que retorna el control a l'usuari. Actualment NeoChat implementa una gran part del protocol amb l'excepció dels xats encriptats i els xats de vídeo.</p>
|
||||
<p xml:lang="de">Matrix ist ein dezentralisiertes Kommunikationsprotokoll, das dem Benutzer wieder die Kontrolle zurückgibt. Derzeit implementiert NeoChat einen großen Teil des Protokolls mit der Ausnahme von verschlüsselten Chats und Video-Chat.</p>
|
||||
<p xml:lang="el">Το Matrix είναι ένα αποκεντρωμένο πρωτόκολλο επικοινωνίας, δίνοντας πίσω στον χρήστη τον έλεγχο. Προς το παρόν το NeoChat υλοποιεί ένα μεγάλο μέρος του πρωτοκόλλου με εξαίρεση τις κρυπτογραφημένες συνομιλίες και τη συνομιλία με βίντεο.</p>
|
||||
<p xml:lang="en-GB">Matrix is a decentralised communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.</p>
|
||||
<p xml:lang="es">Matrix es un protocolo de comunicaciones descentralizado, que devuelve el control al usuario. En la actualidad, NeoChat implementa gran parte del protocolo con la excepción de chats cifrados y chats de vídeo.</p>
|
||||
<p xml:lang="eu">Matrix komunikazio-protokolo deszentralizatu bat da, erabiltzaileari kontrola itzultzen diona. Gaur egun, NeoChat-ek protokoloaren zati handi bat inplementatzen du, berriketa zifratuak eta bideo berriketak izan ezik.</p>
|
||||
<p xml:lang="fi">Matrix on hajautettu viestintäyhteyskäytäntö, joka antaa hallinnan takaisin käyttäjille. NeoChat tarjoaa nykyisellään valtaosan yhteyskäytännöstä salattuja keskustelu- ja videokeskusteluja lukuun ottamatta.</p>
|
||||
<p xml:lang="fr">Matrix est un protocole de communication décentralisé, donnant le contrôle à l'utilisateur. Actuellement, NeoChat met en œuvre une grande partie du protocole, à l'exception des discussions chiffrées et du chat vidéo.</p>
|
||||
<p xml:lang="hu">A Matrix egy decentralizált kommunikációs protokoll, amely a felhasználók kezébe adja az irányítást.</p>
|
||||
<p xml:lang="ia">Matrix es un protocollo de communication decentrate, ponente le usator in le controlo. Currentemente NeoChat implementa un grande parte del protocollo con le exception de conversationes cryptate e conversationes video.</p>
|
||||
<p xml:lang="id">Matrix adalah protokol komunikasi terdesentralisasi, menempatkan pengguna kembali dalam kendali. Saat ini NeoChat mengimplementasikan sebagian besar protokol dengan pengecualian obrolan terenkripsi dan obrolan video.</p>
|
||||
<p xml:lang="ie">Matrix es un decentralisat protocol de communication quel retroda li control a un usator. Actualmen, NeoChat implementa un grand parte del protocol except ciffrat conversationes e videotelefonada.</p>
|
||||
<p xml:lang="it">Matrix è un protocollo di comunicazione decentralizzato, che restituisce all'utente il controllo. Attualmente NeoChat implementa gran parte del protocollo ad eccezione delle chat cifrate e delle chat video.</p>
|
||||
<p xml:lang="ka">Matrix -ი დეცენტრალიზებული კომუნიკაციის პროტოკოლია, რომელიც მომხმარებელს კონტროლის უფლებას უბრუნებს. ამჟამად NeoChat-ს პროტოკოლის ძირითადი ნაწილის მხარდაჭერა გააჩნია, დაშიფვრისა და ვიდეოჩატის გამოკლებით.</p>
|
||||
<p xml:lang="ko">Matrix는 사용자에게 제어권을 돌려 주는 분산 통신 프로토콜입니다. NeoChat은 암호화된 대화 및 영상 통화를 제외한 프로토콜의 대부분 기능을 구현합니다.</p>
|
||||
<p xml:lang="nl">Matrix is een gedecentraliseerd communicatieprotocol, dat de gebruiker de controle teruggeeft. Op dit moment implementeert NeoChat grote delen van het protocol met de uitzondering van versleutelde chats en video-chat.</p>
|
||||
<p xml:lang="pl">Matrix jest protokołem rozproszonego porozumiewania się oddający użytkownikowi jego władzę. Obecnie NeoChat obsługuje dużą część protokołu poza szyfrowanymi rozmowami tekstowymi i z obrazem.</p>
|
||||
<p xml:lang="pt">O Matrix é um protocolo de comunicações descentralizado, colocando de novo o utilizador no poder. De momento, o NeoChat implementa uma boa parte do protocolo, com a excepção das conversas encriptadas e as conversas de vídeo.</p>
|
||||
<p xml:lang="pt-BR">O Matrix é um protocolo de comunicação descentralizado, colocando o usuário de volta no controle. Atualmente o NeoChat implementa grande parte do protocolo com a exceção de bate-papos criptografados e bate-papo por vídeo.</p>
|
||||
<p xml:lang="ru">Matrix — это децентрализованный коммуникационный протокол, возвращающий пользователю контроль над своими данными. В настоящее время в приложении NeoChat реализована поддержка большей части протокола, за исключением зашифрованных чатов и видеочата.</p>
|
||||
<p xml:lang="sk">Matrix je decentralizovaný komunikačný protokol, ktorý používateľovi vracia kontrolu. V súčasnosti NeoChat implementuje veľkú časť protokolu s výnimkou šifrovaných chatov a videohovorov.</p>
|
||||
<p xml:lang="sl">Matrix je decentraliziran komunikacijski protokol, kjer ima uporabnik uporabnik kontrolo rabe. Trenutno ima NeoChat izveden velik del protokola z izjemo šifriranih klepetov in video klepetov.</p>
|
||||
<p xml:lang="sv">Matrix är ett decentraliserat kommunikationsprotokoll, som ger tillbaka kontrollen till användaren. För närvarande implementerar NeoChat en stor del av protokollet, med undantag för krypterad chatt och videochatt.</p>
|
||||
<p xml:lang="tr">Matrix; tam denetimi kullanıcıya bırakan, merkezi olmayan bir iletişim protokolüdür. Şu anda NeoChat, uçtan uca şifrelenmiş metin ve video sohbetleri dışında protokolün büyük bir bölümünü bünyesinde bulundurur.</p>
|
||||
<p xml:lang="uk">Matrix — протокол децентралізованого спілкування, який передає контроль над даними користувачеві. У поточній версії NeoChat реалізовано більшу частину протоколу, окрім зашифрованого спілкування та відеоспілкування.</p>
|
||||
<p xml:lang="x-test">xxMatrix is a decentralized communication protocol, putting the user back in control. Currently NeoChat implements large part of the protocol with the exception of encrypted chats and video chat.xx</p>
|
||||
<p xml:lang="zh-CN">Matrix 是一个分布式通讯协议,使用户重新得到控制权。 目前,NeoChat 实现了协议的大部分,除了加密聊天和视频聊天。</p>
|
||||
<p>NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||
<p xml:lang="ar">يعمل نيوتشات على كل من الأجهزة المحمولة وسطح المكتب مع توفير تجربة مستخدم متسقة.</p>
|
||||
<p xml:lang="az">Vahid istifadəçi interfeysi ilə təmin olunan NeoChat, həm mobil telefonda həm də kompyuterlərdə işləyir.</p>
|
||||
<p xml:lang="ca">El NeoChat funciona en els mòbils i a l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat funciona en els mòbils i en l'escriptori, proporcionant una experiència d'usuari coherent.</p>
|
||||
<p xml:lang="de">NeoChat funktioniert sowohl auf Mobilgeräten als auch auf dem PC und bietet ein einheitliches Benutzererlebnis.</p>
|
||||
<p xml:lang="el">Το NeoChat λειτουργεί και στα κινητά και στους υπολογιστές γραφείου παρέχοντας μια αδιάλειπτη εμπειρία χρήσης.</p>
|
||||
<p xml:lang="en-GB">NeoChat works both on mobile and desktop while providing a consistent user experience.</p>
|
||||
<p xml:lang="es">NeoChat funciona en móviles y en el escritorio a la vez que proporciona una experiencia de usuario consistente.</p>
|
||||
<p xml:lang="eu">NeoChat mugikorretan eta mahaigainean dabil, erabiltzaile esperientzia koherentea eskainiz.</p>
|
||||
<p xml:lang="fi">NeoChat toimii sekä mobiili- että työpöytäalustoilla tarjoten yhdenmukaisen käyttökokemuksen.</p>
|
||||
<p xml:lang="fr">NeoChat fonctionne aussi bien sur les mobiles que sur les ordinateurs de bureau, tout en offrant une expérience utilisateur cohérente.</p>
|
||||
<p xml:lang="hu">A NeoChat mobilon és asztali számítógépen is működik, egységes felhasználói élményt nyújtva.</p>
|
||||
<p xml:lang="ia">NeoChat functiona sia sur mobile que ur scriptorio durante que forni un experientia de usator consistente.</p>
|
||||
<p xml:lang="id">NeoChat berfungsi baik di ponsel dan desktop sambil memberikan pengalaman pengguna yang konsisten.</p>
|
||||
<p xml:lang="ie">NeoChat supporta amb computatores e aparates mobil, ancor providente un consistent experientie de usator.</p>
|
||||
<p xml:lang="it">NeoChat funziona sia su dispositivi mobili che desktop, fornendo un'esperienza utente coerente.</p>
|
||||
<p xml:lang="ka">NeoChat მუშაობს როგორც მობილურ, ისე ჩვეულებრივ კომპიუტერებზე იმ დროს, როცა მომხმარებლის ინტერფეისი არ იცვლება.</p>
|
||||
<p xml:lang="ko">NeoChat은 모바일과 데스크톱 모두에서 일관된 사용자 경험을 제공합니다.</p>
|
||||
<p xml:lang="nl">NeoChat werkt zowel op de mobiel en het bureaublad met het leveren van een consistente gebruikerservaring.</p>
|
||||
<p xml:lang="pl">NeoChat działa zarówno na urządzeniach przenośnych jak i biurkowych, zapewniając spójne wrażenia użytkownika</p>
|
||||
<p xml:lang="pt">O NeoChat funciona tanto em dispositivos móveis como no computador, fornecendo uma experiência de utilizador consistente.</p>
|
||||
<p xml:lang="pt-BR">O NeoChat funciona tanto no celular como no computador enquanto fornece uma experiência consistente ao usuário.</p>
|
||||
<p xml:lang="ru">NeoChat работает как на мобильных устройствах, так и на настольных компьютерах, обеспечивая единый пользовательский интерфейс.</p>
|
||||
<p xml:lang="sk">NeoChat funguje na mobilných aj stolových počítačoch a poskytuje konzistentný používateľský zážitok.</p>
|
||||
<p xml:lang="sl">NeoChat deluje tako na mobilnih kot na namiznih platformah z zagotavljanjem konsistentne uporabniške izkušnje.</p>
|
||||
<p xml:lang="sv">NeoChat fungerar både på mobil och skrivbord och tillhandahåller en konsekvent användarupplevelse.</p>
|
||||
<p xml:lang="tr">NeoChat, hem masaüstü hem de taşınabilir ortamlarda çalışarak tutarlı bir kullanıcı deneyimi sunar.</p>
|
||||
<p xml:lang="uk">NeoChat працює на мобільних пристроях та звичайних комп'ютерах, маючи однорідний інтерфейс на усіх підтримуваних пристроях.</p>
|
||||
<p xml:lang="x-test">xxNeoChat works both on mobile and desktop while providing a consistent user experience.xx</p>
|
||||
<p xml:lang="zh-CN">NeoChat 在移动设备和桌面上均可用,并提供一致的用户体验。</p>
|
||||
</description>
|
||||
<url type="homepage">https://apps.kde.org/neochat/</url>
|
||||
<url type="bugtracker">https://bugs.kde.org/buglist.cgi?component=General&product=NeoChat</url>
|
||||
@@ -217,16 +231,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="23.04.1" date="2023-05-11"/>
|
||||
<release version="23.04.0" date="2023-04-20">
|
||||
<artifacts>
|
||||
<artifact type="binary" platform="x86_64-windows-msvc">
|
||||
<location>https://download.kde.org/stable/release-service/23.04.0/windows/neochat-23.04.0-512-windows-cl-msvc2019-x86_64.exe</location>
|
||||
<checksum type="sha256">85e17fdd9b128bfdd171a93b11f7a95febeff0a00df9261df11f6ff67637686d</checksum>
|
||||
<size type="download">58032464</size>
|
||||
</artifact>
|
||||
</artifacts>
|
||||
</release>
|
||||
<release version="23.04.0" date="2023-04-20"/>
|
||||
<release version="23.01" date="2023-01-30">
|
||||
<url>https://plasma-mobile.org/2023/01/30/january-blog-post/</url>
|
||||
<description>
|
||||
|
||||
799
po/ar/neochat.po
799
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
789
po/az/neochat.po
789
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
810
po/ca/neochat.po
810
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
769
po/cs/neochat.po
769
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
758
po/da/neochat.po
758
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
792
po/de/neochat.po
792
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
787
po/el/neochat.po
787
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
774
po/es/neochat.po
774
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
796
po/eu/neochat.po
796
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
814
po/fi/neochat.po
814
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
774
po/fr/neochat.po
774
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
2238
po/hu/neochat.po
2238
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
826
po/ia/neochat.po
826
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
788
po/id/neochat.po
788
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
783
po/ie/neochat.po
783
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
795
po/it/neochat.po
795
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
750
po/ja/neochat.po
750
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
774
po/ka/neochat.po
774
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
787
po/ko/neochat.po
787
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
752
po/lt/neochat.po
752
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
774
po/nl/neochat.po
774
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
705
po/nn/neochat.po
705
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
782
po/pa/neochat.po
782
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
804
po/pl/neochat.po
804
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
780
po/pt/neochat.po
780
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1268
po/ru/neochat.po
1268
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
785
po/sk/neochat.po
785
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
780
po/sl/neochat.po
780
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
794
po/sv/neochat.po
794
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
770
po/ta/neochat.po
770
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
764
po/tr/neochat.po
764
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
776
po/uk/neochat.po
776
po/uk/neochat.po
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
@@ -30,9 +30,11 @@ add_library(neochat STATIC
|
||||
models/devicesmodel.cpp
|
||||
filetypesingleton.cpp
|
||||
login.cpp
|
||||
stickerevent.cpp
|
||||
models/webshortcutmodel.cpp
|
||||
blurhash.cpp
|
||||
blurhashimageprovider.cpp
|
||||
joinrulesevent.cpp
|
||||
models/collapsestateproxymodel.cpp
|
||||
urlhelper.cpp
|
||||
windowcontroller.cpp
|
||||
@@ -42,23 +44,9 @@ add_library(neochat STATIC
|
||||
models/actionsmodel.cpp
|
||||
models/serverlistmodel.cpp
|
||||
models/statemodel.cpp
|
||||
models/statefiltermodel.cpp
|
||||
filetransferpseudojob.cpp
|
||||
models/searchmodel.cpp
|
||||
texthandler.cpp
|
||||
logger.cpp
|
||||
models/stickermodel.cpp
|
||||
models/imagepacksmodel.cpp
|
||||
events/imagepackevent.cpp
|
||||
events/joinrulesevent.cpp
|
||||
events/stickerevent.cpp
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(neochat
|
||||
HEADER "messageeventmodel_logging.h"
|
||||
IDENTIFIER "MessageEvent"
|
||||
CATEGORY_NAME "org.kde.neochat.messageeventmodel"
|
||||
DEFAULT_SEVERITY Info
|
||||
)
|
||||
|
||||
add_executable(neochat-app
|
||||
@@ -72,11 +60,11 @@ target_link_libraries(neochat-app PRIVATE
|
||||
neochat
|
||||
)
|
||||
|
||||
if(Quotient${QUOTIENT_SUFFIX}_VERSION_MINOR GREATER 6)
|
||||
if(Quotient_VERSION_MINOR GREATER 6)
|
||||
target_compile_definitions(neochat PUBLIC QUOTIENT_07)
|
||||
target_sources(neochat PRIVATE events/pollevent.cpp pollhandler.cpp)
|
||||
target_sources(neochat PRIVATE pollevent.cpp pollhandler.cpp)
|
||||
else()
|
||||
target_compile_definitions(neochat PUBLIC QUOTIENT_VERSION=\"${Quotient${QUOTIENT_SUFFIX}_VERSION}\")
|
||||
target_compile_definitions(neochat PUBLIC QUOTIENT_VERSION=\"${Quotient_VERSION}\")
|
||||
target_sources(neochat PRIVATE neochataccountregistry.cpp)
|
||||
endif()
|
||||
|
||||
@@ -106,7 +94,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient${QUOTIENT_SUFFIX} cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#include "actionshandler.h"
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <csapi/joining.h>
|
||||
#include <events/roommemberevent.h>
|
||||
|
||||
@@ -11,10 +13,13 @@
|
||||
#include <KLocalizedString>
|
||||
#include <QStringBuilder>
|
||||
|
||||
#include "controller.h"
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "roommanager.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -12,31 +12,13 @@
|
||||
class CustomEmojiModel;
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ActionsHandler
|
||||
*
|
||||
* This class handles chat messages ready for posting to a room.
|
||||
*
|
||||
* Everything that needs to be done to prepare the message for posting in a room
|
||||
* including:
|
||||
* - File handling
|
||||
* - User mentions
|
||||
* - Quick edits
|
||||
* - Chat actions
|
||||
* - Custom emojis
|
||||
*
|
||||
* @note A chat action is a message starting with /, resulting in something other
|
||||
* than a normal message being sent (e.g. /me, /join).
|
||||
*
|
||||
* @sa ActionsModel, NeoChatRoom
|
||||
*/
|
||||
/// \brief Handles user interactions with NeoChat (joining room, creating room,
|
||||
/// sending message). Account management is handled by Controller.
|
||||
class ActionsHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The room that messages will be sent to.
|
||||
*/
|
||||
/// \brief The room that messages will be sent to.
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
|
||||
@@ -5,24 +5,6 @@
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/**
|
||||
* @brief Returns the pixel array of the result image given the blurhash string.
|
||||
*
|
||||
* @param blurhash a string representing the blurhash to be decoded.
|
||||
* @param width the width of the resulting image.
|
||||
* @param height the height of the resulting image.
|
||||
* @param punch the factor to improve the contrast, default = 1.
|
||||
* @param nChannels the number of channels in the resulting image array, 3 = RGB, 4 = RGBA.
|
||||
*
|
||||
* @return A pointer to memory region where pixels are stored in (H, W, C) format.
|
||||
*/
|
||||
uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels);
|
||||
|
||||
/**
|
||||
* @brief Checks if the Blurhash is valid or not.
|
||||
*
|
||||
* @param blurhash a string representing the blurhash.
|
||||
*
|
||||
* @return A bool (true if it is a valid blurhash, else false).
|
||||
*/
|
||||
bool isValidBlurhash(const char *blurhash);
|
||||
|
||||
@@ -5,22 +5,9 @@
|
||||
|
||||
#include <QQuickImageProvider>
|
||||
|
||||
/**
|
||||
* @class BlurhashImageProvider
|
||||
*
|
||||
* A QQuickImageProvider for blurhashes.
|
||||
*
|
||||
* @sa QQuickImageProvider
|
||||
*/
|
||||
class BlurhashImageProvider : public QQuickImageProvider
|
||||
{
|
||||
public:
|
||||
BlurhashImageProvider();
|
||||
|
||||
/**
|
||||
* @brief Return an image for a given ID.
|
||||
*
|
||||
* @sa QQuickImageProvider::requestImage
|
||||
*/
|
||||
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||
};
|
||||
};
|
||||
@@ -14,6 +14,8 @@
|
||||
#include <Sonnet/BackgroundChecker>
|
||||
#include <Sonnet/Settings>
|
||||
|
||||
#include "models/actionsmodel.h"
|
||||
#include "models/roomlistmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
class SyntaxHighlighter : public QSyntaxHighlighter
|
||||
|
||||
@@ -8,54 +8,13 @@
|
||||
#include <QTextCursor>
|
||||
|
||||
#include "models/completionmodel.h"
|
||||
#include "models/userlistmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
class QTextDocument;
|
||||
class NeoChatRoom;
|
||||
class SyntaxHighlighter;
|
||||
|
||||
/**
|
||||
* @class ChatDocumentHandler
|
||||
*
|
||||
* Handle the QQuickTextDocument of a qml text item.
|
||||
*
|
||||
* The class provides functionality to highlight text in the text document as well
|
||||
* as providing completion functionality via a CompletionModel.
|
||||
*
|
||||
* The ChatDocumentHandler is also linked to a NeoChatRoom to provide functionality
|
||||
* to save the chat document text when switching between rooms.
|
||||
*
|
||||
* To get the full functionality the cursor position and text selection information
|
||||
* need to be passed in. For example:
|
||||
*
|
||||
* @code{.qml}
|
||||
* import QtQuick 2.0
|
||||
* import QtQuick.Controls 2.15 as QQC2
|
||||
*
|
||||
* import org.kde.kirigami 2.12 as Kirigami
|
||||
* import org.kde.neochat 1.0
|
||||
*
|
||||
* QQC2.TextArea {
|
||||
* id: textField
|
||||
*
|
||||
* // Set this to a NeoChatRoom object.
|
||||
* property var room
|
||||
*
|
||||
* ChatDocumentHandler {
|
||||
* id: documentHandler
|
||||
* document: textField.textDocument
|
||||
* cursorPosition: textField.cursorPosition
|
||||
* selectionStart: textField.selectionStart
|
||||
* selectionEnd: textField.selectionEnd
|
||||
* mentionColor: Kirigami.Theme.linkColor
|
||||
* errorColor: Kirigami.Theme.negativeTextColor
|
||||
* room: textField.room
|
||||
* }
|
||||
* }
|
||||
* @endcode
|
||||
*
|
||||
* @sa QQuickTextDocument, CompletionModel, NeoChatRoom
|
||||
*/
|
||||
class ChatDocumentHandler : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -67,48 +26,16 @@ class ChatDocumentHandler : public QObject
|
||||
* from the correct parameters in the assigned room.
|
||||
*/
|
||||
Q_PROPERTY(bool isEdit READ isEdit WRITE setIsEdit NOTIFY isEditChanged)
|
||||
|
||||
/**
|
||||
* @brief The QQuickTextDocument that is being handled.
|
||||
*/
|
||||
Q_PROPERTY(QQuickTextDocument *document READ document WRITE setDocument NOTIFY documentChanged)
|
||||
|
||||
/**
|
||||
* @brief The current saved cursor position.
|
||||
*/
|
||||
Q_PROPERTY(int cursorPosition READ cursorPosition WRITE setCursorPosition NOTIFY cursorPositionChanged)
|
||||
|
||||
/**
|
||||
* @brief The start position of any currently selected text.
|
||||
*/
|
||||
Q_PROPERTY(int selectionStart READ selectionStart WRITE setSelectionStart NOTIFY selectionStartChanged)
|
||||
|
||||
/**
|
||||
* @brief The end position of any currently selected text.
|
||||
*/
|
||||
Q_PROPERTY(int selectionEnd READ selectionEnd WRITE setSelectionEnd NOTIFY selectionEndChanged)
|
||||
|
||||
/**
|
||||
* @brief The current CompletionModel.
|
||||
*
|
||||
* This is typically provided to a qml component to visualise the current
|
||||
* completion results.
|
||||
*/
|
||||
Q_PROPERTY(CompletionModel *completionModel READ completionModel NOTIFY completionModelChanged)
|
||||
|
||||
/**
|
||||
* @brief The current room that the the text document is being handled for.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
/**
|
||||
* @brief The color to highlight user mentions.
|
||||
*/
|
||||
Q_PROPERTY(QColor mentionColor READ mentionColor WRITE setMentionColor NOTIFY mentionColorChanged);
|
||||
|
||||
/**
|
||||
* @brief The color to highlight spelling errors.
|
||||
*/
|
||||
Q_PROPERTY(QColor errorColor READ errorColor WRITE setErrorColor NOTIFY errorColorChanged);
|
||||
|
||||
public:
|
||||
@@ -157,11 +84,11 @@ Q_SIGNALS:
|
||||
private:
|
||||
int completionStartIndex() const;
|
||||
|
||||
bool m_isEdit = false;
|
||||
bool m_isEdit;
|
||||
|
||||
QQuickTextDocument *m_document;
|
||||
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
bool completionVisible = false;
|
||||
|
||||
QColor m_mentionColor;
|
||||
|
||||
@@ -62,10 +62,10 @@ QString Clipboard::saveImage(QString localPath) const
|
||||
void Clipboard::saveText(QString message)
|
||||
{
|
||||
static QRegularExpression re(QStringLiteral("<[^>]*>"));
|
||||
auto *mimeData = new QMimeData; // ownership is transferred to clipboard
|
||||
mimeData->setHtml(message);
|
||||
mimeData->setText(message.replace(re, QString()));
|
||||
m_clipboard->setMimeData(mimeData);
|
||||
auto *mineData = new QMimeData; // ownership is transferred to clipboard
|
||||
mineData->setHtml(message);
|
||||
mineData->setText(message.replace(re, QString()));
|
||||
m_clipboard->setMimeData(mineData);
|
||||
}
|
||||
|
||||
void Clipboard::setImage(const QUrl &url)
|
||||
|
||||
@@ -9,60 +9,23 @@ class QClipboard;
|
||||
class QImage;
|
||||
|
||||
/**
|
||||
* @class Clipboard
|
||||
*
|
||||
* Clipboard proxy
|
||||
*
|
||||
* Used to set and retrieve content from the clipboard.
|
||||
*/
|
||||
class Clipboard : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief Whether the current clipboard content is an image.
|
||||
*/
|
||||
Q_PROPERTY(bool hasImage READ hasImage NOTIFY imageChanged)
|
||||
|
||||
/**
|
||||
* @brief Return the current clipboard content image.
|
||||
*
|
||||
* Returns a null image if the clipboard does not contain an image or if it
|
||||
* contains an image in an unsupported image format.
|
||||
*/
|
||||
Q_PROPERTY(QImage image READ image NOTIFY imageChanged)
|
||||
|
||||
public:
|
||||
explicit Clipboard(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] bool hasImage() const;
|
||||
|
||||
[[nodiscard]] QImage image() const;
|
||||
|
||||
/**
|
||||
* @brief Save the current clipboard image to file.
|
||||
*
|
||||
* If the clipboard does not contain an image or if it contains an image in an
|
||||
* unsupported image format nothing happens.
|
||||
*
|
||||
* The given file path must be both valid and local or nothing happens.
|
||||
*
|
||||
* @param localPath the path to save the image. A default path for the app cache
|
||||
* will be used if available and this is empty.
|
||||
*
|
||||
* @return A QString with the path that the image was saved to. The string will
|
||||
* be empty if nothing was saved.
|
||||
*/
|
||||
Q_INVOKABLE QString saveImage(QString localPath = {}) const;
|
||||
|
||||
/**
|
||||
* @brief Set the clipboard content to the input message.
|
||||
*/
|
||||
Q_INVOKABLE void saveText(QString message);
|
||||
|
||||
/**
|
||||
* @brief Set the clipboard content to the input image.
|
||||
*/
|
||||
Q_INVOKABLE void setImage(const QUrl &image);
|
||||
|
||||
private:
|
||||
|
||||
@@ -8,53 +8,18 @@
|
||||
class QAbstractItemModel;
|
||||
class KColorSchemeManager;
|
||||
|
||||
/**
|
||||
* @class ColorSchemer
|
||||
*
|
||||
* A class to provide a wrapper around KColorSchemeManager to make it available in
|
||||
* QML.
|
||||
*
|
||||
* @sa KColorSchemeManager
|
||||
*/
|
||||
class ColorSchemer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief A QAbstractItemModel of all available color schemes.
|
||||
*
|
||||
* @sa QAbstractItemModel
|
||||
*/
|
||||
Q_PROPERTY(QAbstractItemModel *model READ model CONSTANT)
|
||||
|
||||
public:
|
||||
ColorSchemer(QObject *parent = nullptr);
|
||||
~ColorSchemer();
|
||||
|
||||
QAbstractItemModel *model() const;
|
||||
|
||||
/**
|
||||
* @brief Activates the KColorScheme identified by the provided index.
|
||||
*
|
||||
* @sa KColorScheme
|
||||
*/
|
||||
Q_INVOKABLE void apply(int idx);
|
||||
|
||||
/**
|
||||
* @brief Activates the KColorScheme with the given name.
|
||||
*
|
||||
* @sa KColorScheme
|
||||
*/
|
||||
Q_INVOKABLE void apply(const QString &name);
|
||||
|
||||
/**
|
||||
* @brief Returns the index for the scheme with the given name.
|
||||
*/
|
||||
Q_INVOKABLE int indexForScheme(const QString &name) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the name for the scheme with the given index.
|
||||
*/
|
||||
Q_INVOKABLE QString nameForIndex(int index) const;
|
||||
|
||||
private:
|
||||
|
||||
@@ -73,8 +73,24 @@ Controller::Controller(QObject *parent)
|
||||
setApplicationProxy();
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
setQuitOnLastWindowClosed();
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, &Controller::setQuitOnLastWindowClosed);
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
QGuiApplication::setQuitOnLastWindowClosed(false);
|
||||
}
|
||||
connect(NeoChatConfig::self(), &NeoChatConfig::SystemTrayChanged, this, [this]() {
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
} else {
|
||||
disconnect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
|
||||
});
|
||||
#endif
|
||||
|
||||
QTimer::singleShot(0, this, [this] {
|
||||
@@ -137,20 +153,20 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||
if (!initial.contains(connection->user()->id())) {
|
||||
initial.append(connection->user()->id());
|
||||
for (const auto &n : notifications) {
|
||||
oldNotifications += n["event"]["event_id"].toString();
|
||||
oldNotifications += n.toObject()["event"].toObject()["event_id"].toString();
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const auto &n : notifications) {
|
||||
const auto ¬ification = n.toObject();
|
||||
const auto notification = n.toObject();
|
||||
if (notification["read"].toBool()) {
|
||||
oldNotifications.removeOne(notification["event"]["event_id"].toString());
|
||||
oldNotifications.removeOne(notification["event"].toObject()["event_id"].toString());
|
||||
continue;
|
||||
}
|
||||
if (oldNotifications.contains(notification["event"]["event_id"].toString())) {
|
||||
if (oldNotifications.contains(notification["event"].toObject()["event_id"].toString())) {
|
||||
continue;
|
||||
}
|
||||
oldNotifications += notification["event"]["event_id"].toString();
|
||||
oldNotifications += notification["event"].toObject()["event_id"].toString();
|
||||
auto room = connection->room(notification["room_id"].toString());
|
||||
|
||||
// If room exists, room is NOT active OR the application is NOT active, show notification
|
||||
@@ -158,20 +174,25 @@ void Controller::handleNotifications(QPointer<Quotient::Connection> connection)
|
||||
&& !(RoomManager::instance().currentRoom() && room->id() == RoomManager::instance().currentRoom()->id()
|
||||
&& QGuiApplication::applicationState() == Qt::ApplicationActive)) {
|
||||
// The room might have been deleted (for example rejected invitation).
|
||||
auto sender = room->user(notification["event"]["sender"].toString());
|
||||
auto sender = room->user(notification["event"].toObject()["sender"].toString());
|
||||
|
||||
QString body;
|
||||
|
||||
if (notification["event"]["type"].toString() == "org.matrix.msc3381.poll.start") {
|
||||
body = notification["event"]["content"]["org.matrix.msc3381.poll.start"]["question"]["body"].toString();
|
||||
if (notification["event"].toObject()["type"].toString() == "org.matrix.msc3381.poll.start") {
|
||||
body = notification["event"]
|
||||
.toObject()["content"]
|
||||
.toObject()["org.matrix.msc3381.poll.start"]
|
||||
.toObject()["question"]
|
||||
.toObject()["body"]
|
||||
.toString();
|
||||
} else {
|
||||
body = notification["event"]["content"]["body"].toString();
|
||||
body = notification["event"].toObject()["content"].toObject()["body"].toString();
|
||||
}
|
||||
|
||||
if (notification["event"]["type"] == "m.room.encrypted") {
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
const auto decrypted = connection->decryptNotification(notification);
|
||||
body = decrypted["content"]["body"].toString();
|
||||
auto decrypted = connection->decryptNotification(notification);
|
||||
body = decrypted["content"].toObject()["body"].toString();
|
||||
#endif
|
||||
if (body.isEmpty()) {
|
||||
body = i18n("Encrypted Message");
|
||||
@@ -207,6 +228,38 @@ void Controller::showWindow()
|
||||
WindowController::instance().showAndRaiseWindow(QString());
|
||||
}
|
||||
|
||||
void Controller::loginWithAccessToken(const QString &serverAddr, const QString &user, const QString &token, const QString &deviceName)
|
||||
{
|
||||
if (user.isEmpty() || token.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QUrl serverUrl(serverAddr);
|
||||
|
||||
auto conn = new Connection();
|
||||
if (serverUrl.isValid()) {
|
||||
conn->setHomeserver(serverUrl);
|
||||
}
|
||||
|
||||
connect(conn, &Connection::connected, this, [this, conn, deviceName] {
|
||||
AccountSettings account(conn->userId());
|
||||
account.setKeepLoggedIn(true);
|
||||
account.setHomeserver(conn->homeserver());
|
||||
account.setDeviceId(conn->deviceId());
|
||||
account.setDeviceName(deviceName);
|
||||
if (!saveAccessTokenToKeyChain(account, conn->accessToken())) {
|
||||
qWarning() << "Couldn't save access token";
|
||||
}
|
||||
account.sync();
|
||||
addConnection(conn);
|
||||
setActiveConnection(conn);
|
||||
});
|
||||
connect(conn, &Connection::networkError, this, [this](QString error, const QString &, int, int) {
|
||||
Q_EMIT errorOccured(i18n("Network Error: %1", error));
|
||||
});
|
||||
conn->assumeIdentity(user, token, deviceName);
|
||||
}
|
||||
|
||||
void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
{
|
||||
if (!conn) {
|
||||
@@ -249,6 +302,8 @@ void Controller::addConnection(Connection *c)
|
||||
c->setLazyLoading(true);
|
||||
|
||||
connect(c, &Connection::syncDone, this, [this, c] {
|
||||
setBusy(false);
|
||||
|
||||
Q_EMIT syncDone();
|
||||
|
||||
c->sync(30000);
|
||||
@@ -264,6 +319,8 @@ void Controller::addConnection(Connection *c)
|
||||
}
|
||||
});
|
||||
|
||||
setBusy(true);
|
||||
|
||||
c->sync();
|
||||
|
||||
Q_EMIT connectionAdded(c);
|
||||
@@ -494,24 +551,31 @@ int Controller::accountCount() const
|
||||
return AccountRegistry::instance().count();
|
||||
}
|
||||
|
||||
void Controller::setQuitOnLastWindowClosed()
|
||||
bool Controller::quitOnLastWindowClosed()
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
} else {
|
||||
if (m_trayIcon) {
|
||||
disconnect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
return QGuiApplication::quitOnLastWindowClosed();
|
||||
}
|
||||
|
||||
void Controller::setQuitOnLastWindowClosed(bool value)
|
||||
{
|
||||
if (quitOnLastWindowClosed() != value) {
|
||||
QGuiApplication::setQuitOnLastWindowClosed(value);
|
||||
Q_EMIT quitOnLastWindowClosedChanged();
|
||||
}
|
||||
QGuiApplication::setQuitOnLastWindowClosed(!NeoChatConfig::self()->systemTray());
|
||||
#else
|
||||
return;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Controller::busy() const
|
||||
{
|
||||
return m_busy;
|
||||
}
|
||||
|
||||
void Controller::setBusy(bool busy)
|
||||
{
|
||||
if (m_busy == busy) {
|
||||
return;
|
||||
}
|
||||
m_busy = busy;
|
||||
Q_EMIT busyChanged();
|
||||
}
|
||||
|
||||
Connection *Controller::activeConnection() const
|
||||
@@ -667,6 +731,11 @@ bool Controller::hasWindowSystem() const
|
||||
#endif
|
||||
}
|
||||
|
||||
QString Controller::plainText(QQuickTextDocument *document) const
|
||||
{
|
||||
return document->textDocument()->toPlainText();
|
||||
}
|
||||
|
||||
bool Controller::encryptionSupported() const
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
@@ -765,20 +834,4 @@ QString Controller::activeAccountLabel() const
|
||||
return {};
|
||||
}
|
||||
return m_connection->accountDataJson("org.kde.neochat.account_label")["account_label"].toString();
|
||||
}
|
||||
|
||||
QVariantList Controller::getSupportedRoomVersions(Quotient::Connection *connection)
|
||||
{
|
||||
auto roomVersions = connection->availableRoomVersions();
|
||||
|
||||
QVariantList supportedRoomVersions;
|
||||
for (const Quotient::Connection::SupportedRoomVersion &v : roomVersions) {
|
||||
QVariantMap roomVersionMap;
|
||||
roomVersionMap.insert("id", v.id);
|
||||
roomVersionMap.insert("status", v.status);
|
||||
roomVersionMap.insert("isStable", v.isStable());
|
||||
supportedRoomVersions.append(roomVersionMap);
|
||||
}
|
||||
|
||||
return supportedRoomVersions;
|
||||
}
|
||||
}
|
||||
194
src/controller.h
194
src/controller.h
@@ -28,198 +28,104 @@ namespace QKeychain
|
||||
class ReadPasswordJob;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Controller
|
||||
*
|
||||
* A singleton class designed to help manage the application.
|
||||
*
|
||||
* There are also a bunch of helper functions that currently don't fit anywhere
|
||||
* else.
|
||||
*/
|
||||
class Controller : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The number of logged in accounts.
|
||||
*/
|
||||
Q_PROPERTY(int accountCount READ accountCount NOTIFY accountCountChanged)
|
||||
|
||||
/**
|
||||
* @brief The current connection for the rest of NeoChat to use.
|
||||
*/
|
||||
Q_PROPERTY(bool quitOnLastWindowClosed READ quitOnLastWindowClosed WRITE setQuitOnLastWindowClosed NOTIFY quitOnLastWindowClosedChanged)
|
||||
Q_PROPERTY(Quotient::Connection *activeConnection READ activeConnection WRITE setActiveConnection NOTIFY activeConnectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The row number in the accounts directory of the active connection.
|
||||
*/
|
||||
Q_PROPERTY(bool busy READ busy WRITE setBusy NOTIFY busyChanged)
|
||||
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
|
||||
Q_PROPERTY(bool hasWindowSystem READ hasWindowSystem CONSTANT)
|
||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
|
||||
Q_PROPERTY(int activeConnectionIndex READ activeConnectionIndex NOTIFY activeConnectionIndexChanged)
|
||||
|
||||
/**
|
||||
* @brief The account label for the active account.
|
||||
*
|
||||
* Account labels are a concept specific to NeoChat, allowing accounts to be
|
||||
* labelled, e.g. for "Work", "Private", etc.
|
||||
*
|
||||
* Set to an empty string to remove the label.
|
||||
*/
|
||||
Q_PROPERTY(int quotientMinorVersion READ quotientMinorVersion CONSTANT)
|
||||
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
|
||||
Q_PROPERTY(QString activeAccountLabel READ activeAccountLabel WRITE setActiveAccountLabel NOTIFY activeAccountLabelChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the OS NeoChat is running on supports sytem tray icons.
|
||||
*/
|
||||
Q_PROPERTY(bool supportSystemTray READ supportSystemTray CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether KWindowSystem specific features are available.
|
||||
*/
|
||||
Q_PROPERTY(bool hasWindowSystem READ hasWindowSystem CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether NeoChat is currently able to connect to the server.
|
||||
*/
|
||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the ecryption support has been enabled.
|
||||
*/
|
||||
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The current minor version number of libQuotient being used.
|
||||
*
|
||||
* This is the only way to gate NeoChat features by libQuotient version in QML.
|
||||
*
|
||||
* @note No major version because libQuotient doesn't have any; All are 0.x.
|
||||
*/
|
||||
Q_PROPERTY(int quotientMinorVersion READ quotientMinorVersion CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether NeoChat is running as a flatpak.
|
||||
*
|
||||
* This is the only way to gate NeoChat features in flatpaks in QML.
|
||||
*/
|
||||
Q_PROPERTY(bool isFlatpak READ isFlatpak CONSTANT)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the status after an attempt to change the password on an account.
|
||||
*/
|
||||
enum PasswordStatus {
|
||||
Success, /**< The password was successfully changed. */
|
||||
Wrong, /**< The current password entered was wrong. */
|
||||
Other, /**< An unknown problem occurred. */
|
||||
};
|
||||
Q_ENUM(PasswordStatus);
|
||||
|
||||
static Controller &instance();
|
||||
|
||||
[[nodiscard]] int accountCount() const;
|
||||
|
||||
void setActiveConnection(Quotient::Connection *connection);
|
||||
[[nodiscard]] Quotient::Connection *activeConnection() const;
|
||||
|
||||
/**
|
||||
* @brief Add a new connection to the account registry.
|
||||
*/
|
||||
void addConnection(Quotient::Connection *c);
|
||||
|
||||
/**
|
||||
* @brief Drop a connection from the account registry.
|
||||
*/
|
||||
void dropConnection(Quotient::Connection *c);
|
||||
|
||||
int activeConnectionIndex() const;
|
||||
Q_INVOKABLE void loginWithAccessToken(const QString &, const QString &, const QString &, const QString &);
|
||||
|
||||
[[nodiscard]] QString activeAccountLabel() const;
|
||||
void setActiveAccountLabel(const QString &label);
|
||||
|
||||
/**
|
||||
* @brief Save an access token to the keychain for the given account.
|
||||
*/
|
||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
||||
|
||||
/**
|
||||
* @brief Change the password for an account.
|
||||
*
|
||||
* The function emits a passwordStatus signal with a PasswordStatus value when
|
||||
* complete.
|
||||
*
|
||||
* @sa PasswordStatus, passwordStatus
|
||||
*/
|
||||
Q_INVOKABLE void changePassword(Quotient::Connection *connection, const QString ¤tPassword, const QString &newPassword);
|
||||
|
||||
/**
|
||||
* @brief Change the avatar for an account.
|
||||
*/
|
||||
Q_INVOKABLE bool setAvatar(Quotient::Connection *connection, const QUrl &avatarSource);
|
||||
|
||||
/**
|
||||
* @brief Create new room for a group chat.
|
||||
*/
|
||||
Q_INVOKABLE void createRoom(const QString &name, const QString &topic);
|
||||
[[nodiscard]] int accountCount() const;
|
||||
|
||||
/**
|
||||
* @brief Join a room.
|
||||
*/
|
||||
Q_INVOKABLE void joinRoom(const QString &alias);
|
||||
[[nodiscard]] static bool quitOnLastWindowClosed();
|
||||
void setQuitOnLastWindowClosed(bool value);
|
||||
|
||||
/**
|
||||
* @brief Join a direct chat with the given user.
|
||||
*
|
||||
* If a direct chat with the user doesn't exist one is created and then joined.
|
||||
*/
|
||||
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
|
||||
[[nodiscard]] bool busy() const;
|
||||
void setBusy(bool busy);
|
||||
|
||||
[[nodiscard]] bool supportSystemTray() const;
|
||||
|
||||
/**
|
||||
* @brief Set the background blur status of the given item.
|
||||
*/
|
||||
Q_INVOKABLE void setBlur(QQuickItem *item, bool blur);
|
||||
bool saveAccessTokenToKeyChain(const Quotient::AccountSettings &account, const QByteArray &accessToken);
|
||||
|
||||
int activeConnectionIndex() const;
|
||||
|
||||
enum PasswordStatus {
|
||||
Success,
|
||||
Wrong,
|
||||
Other,
|
||||
};
|
||||
Q_ENUM(PasswordStatus);
|
||||
|
||||
/// \brief Create new room for a group chat.
|
||||
Q_INVOKABLE void createRoom(const QString &name, const QString &topic);
|
||||
|
||||
/// \brief Join a room.
|
||||
Q_INVOKABLE void joinRoom(const QString &alias);
|
||||
|
||||
bool isOnline() const;
|
||||
|
||||
Q_INVOKABLE QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const;
|
||||
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
|
||||
|
||||
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
|
||||
|
||||
Q_INVOKABLE void setBlur(QQuickItem *item, bool blur);
|
||||
Q_INVOKABLE QString plainText(QQuickTextDocument *document) const;
|
||||
bool encryptionSupported() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the QNetworkProxy for the application.
|
||||
*
|
||||
* @sa QNetworkProxy::setApplicationProxy
|
||||
*/
|
||||
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
|
||||
|
||||
Q_INVOKABLE void setApplicationProxy();
|
||||
|
||||
int quotientMinorVersion() const;
|
||||
|
||||
bool isFlatpak() const;
|
||||
|
||||
/**
|
||||
* @brief Return a string for the input timestamp.
|
||||
* @brief The label for this account.
|
||||
*
|
||||
* The output format depends on the KFormat::DurationFormatOptions chosen.
|
||||
*
|
||||
* @sa KFormat::DurationFormatOptions
|
||||
* Account labels are a concept Specific to NeoChat, allowing accounts to be labelled, e.g. for "Work", "Private", etc.
|
||||
* @return The label, if it exists, otherwise an empty string
|
||||
*/
|
||||
Q_INVOKABLE QString formatDuration(quint64 msecs, KFormat::DurationFormatOptions options = KFormat::DefaultDuration) const;
|
||||
[[nodiscard]] QString activeAccountLabel() const;
|
||||
|
||||
/**
|
||||
* @brief Return a human readable string for a given input number of bytes.
|
||||
*/
|
||||
Q_INVOKABLE QString formatByteSize(double size, int precision = 1) const;
|
||||
|
||||
/**
|
||||
* @brief Force a QQuickTextDocument to refresh when images are loaded.
|
||||
* @brief Set the label for this account.
|
||||
*
|
||||
* HACK: This is a workaround for QTBUG 93281.
|
||||
* Set to an empty string to remove the label
|
||||
* @sa Controller::activeAccountLabel
|
||||
* @param label The label to use, or an empty string
|
||||
*/
|
||||
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
|
||||
|
||||
Q_INVOKABLE QVariantList getSupportedRoomVersions(Quotient::Connection *connection);
|
||||
void setActiveAccountLabel(const QString &label);
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
QPointer<Quotient::Connection> m_connection;
|
||||
bool m_busy = false;
|
||||
TrayIcon *m_trayIcon = nullptr;
|
||||
|
||||
QKeychain::ReadPasswordJob *loadAccessTokenFromKeyChain(const Quotient::AccountSettings &account);
|
||||
@@ -237,9 +143,9 @@ private:
|
||||
private Q_SLOTS:
|
||||
void invokeLogin();
|
||||
void showWindow();
|
||||
void setQuitOnLastWindowClosed();
|
||||
|
||||
Q_SIGNALS:
|
||||
void busyChanged();
|
||||
/// Error occurred because of user inputs
|
||||
void errorOccured(const QString &error);
|
||||
|
||||
|
||||
@@ -5,13 +5,6 @@
|
||||
|
||||
#include <QVariant>
|
||||
|
||||
/**
|
||||
* @class EmojiTones
|
||||
*
|
||||
* This class provides a _tones variable with the available emoji tones to EmojiModel.
|
||||
*
|
||||
* @sa EmojiModel
|
||||
*/
|
||||
class EmojiTones
|
||||
{
|
||||
private:
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "imagepackevent.h"
|
||||
#include <QJsonObject>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
{
|
||||
if (json.contains(QStringLiteral("pack"))) {
|
||||
pack = ImagePackEventContent::Pack{
|
||||
fromJson<Omittable<QString>>(json["pack"].toObject()["display_name"]),
|
||||
#ifdef QUOTIENT_07
|
||||
fromJson<Omittable<QUrl>>(json["pack"].toObject()["avatar_url"]),
|
||||
#else
|
||||
QUrl(),
|
||||
#endif
|
||||
fromJson<Omittable<QStringList>>(json["pack"].toObject()["usage"]),
|
||||
fromJson<Omittable<QString>>(json["pack"].toObject()["attribution"]),
|
||||
};
|
||||
} else {
|
||||
pack = none;
|
||||
}
|
||||
|
||||
const auto &keys = json["images"].toObject().keys();
|
||||
for (const auto &k : keys) {
|
||||
Omittable<EventContent::ImageInfo> info;
|
||||
if (json["images"][k].toObject().contains(QStringLiteral("info"))) {
|
||||
#ifdef QUOTIENT_07
|
||||
info = EventContent::ImageInfo(QUrl(json["images"][k]["url"].toString()), json["images"][k]["info"].toObject(), k);
|
||||
#else
|
||||
info = EventContent::ImageInfo(QUrl(json["images"][k]["url"].toString()), json["images"][k].toObject(), k);
|
||||
#endif
|
||||
} else {
|
||||
info = none;
|
||||
}
|
||||
images += ImagePackImage{
|
||||
k,
|
||||
#ifdef QUOTIENT_07
|
||||
fromJson<QUrl>(json["images"][k]["url"].toString()),
|
||||
#else
|
||||
QUrl(),
|
||||
#endif
|
||||
fromJson<Omittable<QString>>(json["images"][k]["body"]),
|
||||
info,
|
||||
fromJson<Omittable<QStringList>>(json["images"][k]["usage"]),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void ImagePackEventContent::fillJson(QJsonObject *o) const
|
||||
{
|
||||
// TODO
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QVector>
|
||||
#include <events/eventcontent.h>
|
||||
#include <events/stateevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class ImagePackEventContent
|
||||
*
|
||||
* A class to define the content of an image pack event.
|
||||
*
|
||||
* See Matrix MSC2545 for more details.
|
||||
* https://github.com/Sorunome/matrix-doc/blob/soru/emotes/proposals/2545-emotes.md
|
||||
*
|
||||
* @sa ImagePackEvent
|
||||
*/
|
||||
class ImagePackEventContent
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the properties of an image pack.
|
||||
*/
|
||||
struct Pack {
|
||||
Quotient::Omittable<QString> displayName; /**< The display name of the pack. */
|
||||
Quotient::Omittable<QUrl> avatarUrl; /**< The source mxc URL for the pack avatar. */
|
||||
Quotient::Omittable<QStringList> usage; /**< An array of the usages for this pack. Possible usages are "emoticon" and "sticker". */
|
||||
Quotient::Omittable<QString> attribution; /**< The attribution for the pack author(s). */
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Defines the properties of an image pack image.
|
||||
*/
|
||||
struct ImagePackImage {
|
||||
QString shortcode; /**< The shortcode for the image. */
|
||||
QUrl url; /**< The mxc URL for this image. */
|
||||
Quotient::Omittable<QString> body; /**< An optional text body for this image. */
|
||||
Quotient::Omittable<Quotient::EventContent::ImageInfo> info; /**< The ImageInfo object used for the info block of m.sticker events. */
|
||||
/**
|
||||
* @brief An array of the usages for this image.
|
||||
*
|
||||
* The possible values match those of the usage key of a pack object.
|
||||
*/
|
||||
Quotient::Omittable<QStringList> usage;
|
||||
};
|
||||
|
||||
/**
|
||||
* @brief Return the pack properties.
|
||||
*
|
||||
* @sa Pack
|
||||
*/
|
||||
Quotient::Omittable<Pack> pack;
|
||||
|
||||
/**
|
||||
* @brief Return a vector of images in the pack.
|
||||
*
|
||||
* @sa ImagePackImage
|
||||
*/
|
||||
QVector<ImagePackEventContent::ImagePackImage> images;
|
||||
|
||||
explicit ImagePackEventContent(const QJsonObject &o);
|
||||
|
||||
/**
|
||||
* @brief The definition of how to convert the content to Json.
|
||||
*
|
||||
* This is a specialization of the standard fillJson function from libQuotient.
|
||||
*
|
||||
* @sa Quotient::converters
|
||||
*/
|
||||
void fillJson(QJsonObject *o) const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class ImagePackEvent
|
||||
*
|
||||
* Class to define an image pack state event.
|
||||
*
|
||||
* The event content is ImagePackEventContent.
|
||||
*
|
||||
* @sa Quotient::StateEvent, ImagePackEventContent
|
||||
*/
|
||||
#ifdef QUOTIENT_07
|
||||
class ImagePackEvent : public KeyedStateEventBase<ImagePackEvent, ImagePackEventContent>
|
||||
#else
|
||||
class ImagePackEvent : public StateEvent<ImagePackEventContent>
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
#ifdef QUOTIENT_07
|
||||
QUO_EVENT(ImagePackEvent, "im.ponies.room_emotes")
|
||||
using KeyedStateEventBase::KeyedStateEventBase;
|
||||
#else
|
||||
DEFINE_EVENT_TYPEID("im.ponies.room_emotes", ImagePackEvent)
|
||||
explicit ImagePackEvent(const QJsonObject &obj)
|
||||
: StateEvent(typeId(), obj)
|
||||
{
|
||||
}
|
||||
#endif
|
||||
};
|
||||
|
||||
REGISTER_EVENT_TYPE(ImagePackEvent)
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <events/eventcontent.h>
|
||||
#include <events/roomevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class PollStartEvent
|
||||
*
|
||||
* Class to define a poll start event.
|
||||
*
|
||||
* See MSC3381 for full details on polls in the matrix spec
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/polls/proposals/3381-polls.md.
|
||||
*
|
||||
* @sa Quotient::RoomEvent
|
||||
*/
|
||||
class PollStartEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollStartEvent, "org.matrix.msc3381.poll.start");
|
||||
explicit PollStartEvent(const QJsonObject &obj);
|
||||
|
||||
/**
|
||||
* @brief The maximum number of options a user can select in a poll.
|
||||
*/
|
||||
int maxSelections() const;
|
||||
|
||||
/**
|
||||
* @brief The question being asked in the poll.
|
||||
*/
|
||||
QString question() const;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class PollResponseEvent
|
||||
*
|
||||
* Class to define a poll response event.
|
||||
*
|
||||
* See MSC3381 for full details on polls in the matrix spec
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/polls/proposals/3381-polls.md.
|
||||
*
|
||||
* @sa Quotient::RoomEvent
|
||||
*/
|
||||
class PollResponseEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollResponseEvent, "org.matrix.msc3381.poll.response");
|
||||
explicit PollResponseEvent(const QJsonObject &obj);
|
||||
explicit PollResponseEvent(const QString &pollStartEventId, QStringList responses);
|
||||
};
|
||||
|
||||
/**
|
||||
* @class PollEndEvent
|
||||
*
|
||||
* Class to define a poll end event.
|
||||
*
|
||||
* See MSC3381 for full details on polls in the matrix spec
|
||||
* https://github.com/matrix-org/matrix-spec-proposals/blob/travis/msc/polls/proposals/3381-polls.md.
|
||||
*
|
||||
* @sa Quotient::RoomEvent
|
||||
*/
|
||||
class PollEndEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
QUO_EVENT(PollEndEvent, "org.matrix.msc3381.poll.end");
|
||||
explicit PollEndEvent(const QJsonObject &obj);
|
||||
};
|
||||
}
|
||||
@@ -1,55 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carlschwan@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.1-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <events/eventcontent.h>
|
||||
#include <events/roomevent.h>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class StickerEvent
|
||||
*
|
||||
* Class to define a sticker event.
|
||||
*
|
||||
* Sticker messages are specialised image messages that are displayed without
|
||||
* controls (e.g. no "download" link, or light-box view on click, as would be
|
||||
* displayed for for m.image events).
|
||||
*
|
||||
* @sa Quotient::RoomEvent
|
||||
*/
|
||||
class StickerEvent : public RoomEvent
|
||||
{
|
||||
public:
|
||||
#ifdef QUOTIENT_07
|
||||
QUO_EVENT(StickerEvent, "m.sticker")
|
||||
#else
|
||||
DEFINE_EVENT_TYPEID("m.sticker", StickerEvent)
|
||||
#endif
|
||||
|
||||
explicit StickerEvent(const QJsonObject &obj);
|
||||
|
||||
/**
|
||||
* @brief A textual representation or associated description of the sticker image.
|
||||
*
|
||||
* This could be the alt text of the original image, or a message to accompany
|
||||
* and further describe the sticker.
|
||||
*/
|
||||
QString body() const;
|
||||
|
||||
/**
|
||||
* @brief Metadata about the image referred to in url including a thumbnail representation.
|
||||
*/
|
||||
const EventContent::ImageContent &image() const;
|
||||
|
||||
/**
|
||||
* @brief The URL to the sticker image. This must be a valid mxc:// URI.
|
||||
*/
|
||||
QUrl url() const;
|
||||
|
||||
private:
|
||||
EventContent::ImageContent m_imageContent;
|
||||
};
|
||||
REGISTER_EVENT_TYPE(StickerEvent)
|
||||
}
|
||||
@@ -6,13 +6,6 @@
|
||||
#include <KJob>
|
||||
#include <QString>
|
||||
|
||||
/**
|
||||
* @class FileTransferPseudoJob
|
||||
*
|
||||
* A class inherited from KJob to track a file download.
|
||||
*
|
||||
* @sa KJob
|
||||
*/
|
||||
class FileTransferPseudoJob : public KJob
|
||||
{
|
||||
public:
|
||||
@@ -22,25 +15,10 @@ public:
|
||||
};
|
||||
Q_ENUM(Operation);
|
||||
FileTransferPseudoJob(Operation operation, const QString &srcDest, const QString &path);
|
||||
|
||||
/**
|
||||
* @brief Set the current number of bytes transferred.
|
||||
*/
|
||||
void fileTransferProgress(QString id, qint64 progress, qint64 total);
|
||||
|
||||
/**
|
||||
* @brief Set the file transfer as complete.
|
||||
*/
|
||||
void fileTransferCompleted(QString id, QUrl localFile);
|
||||
|
||||
/**
|
||||
* @brief Set the file transfer as failed.
|
||||
*/
|
||||
void fileTransferFailed(QString id, QString errorMessage = {});
|
||||
|
||||
/**
|
||||
* @brief Start the file transfer.
|
||||
*/
|
||||
void start() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -11,31 +11,11 @@
|
||||
|
||||
class FileTypeSingletonPrivate;
|
||||
|
||||
/**
|
||||
* @class FileTypeSingleton
|
||||
*
|
||||
* Provide a singleton to expose the functionality of QMimeDatabase.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
class FileTypeSingleton : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief List of supported image formats.
|
||||
*
|
||||
* Returns a list of file extensions, not MIME types.
|
||||
*/
|
||||
Q_PROPERTY(QStringList supportedImageFormats READ supportedImageFormats CONSTANT FINAL)
|
||||
|
||||
/**
|
||||
* @brief List of supported animated image formats.
|
||||
*
|
||||
* Returns a list of file extensions, not MIME types.
|
||||
*/
|
||||
Q_PROPERTY(QStringList supportedAnimatedImageFormats READ supportedAnimatedImageFormats CONSTANT FINAL)
|
||||
|
||||
QML_NAMED_ELEMENT(FileType)
|
||||
QML_SINGLETON
|
||||
|
||||
@@ -43,79 +23,26 @@ public:
|
||||
explicit FileTypeSingleton(QObject *parent = nullptr);
|
||||
~FileTypeSingleton();
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for nameOrAlias or an invalid one if none found.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
// Most of the code in this public section was copy/pasted from qmimedatabase.h
|
||||
Q_INVOKABLE QMimeType mimeTypeForName(const QString &nameOrAlias) const;
|
||||
|
||||
enum MatchMode { MatchDefault, MatchExtension, MatchContent };
|
||||
Q_ENUM(MatchMode)
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for the file named fileName using mode.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForFile(const QString &fileName, FileTypeSingleton::MatchMode mode = MatchDefault) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for fileInfo.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForFile(const QFileInfo &fileInfo, FileTypeSingleton::MatchMode mode = MatchDefault) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the MIME types for the file name fileName.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QList<QMimeType> mimeTypesForFileName(const QString &fileName) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for data.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForData(const QByteArray &data) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for the data in device.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForData(QIODevice *device) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for url.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForUrl(const QUrl &url) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for the given fileName and device data.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const;
|
||||
|
||||
/**
|
||||
* @brief Returns a MIME type for the given fileName and device data.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QMimeType mimeTypeForFileNameAndData(const QString &fileName, const QByteArray &data) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the suffix for the file fileName, as known by the MIME database.
|
||||
*
|
||||
* @sa QMimeDatabase
|
||||
*/
|
||||
Q_INVOKABLE QString suffixForFileName(const QString &fileName) const;
|
||||
|
||||
// These return a list of file extensions, not mimetypes
|
||||
QStringList supportedImageFormats() const;
|
||||
QStringList supportedAnimatedImageFormats() const;
|
||||
|
||||
|
||||
@@ -7,13 +7,6 @@
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
/**
|
||||
* @class JoinRulesEvent
|
||||
*
|
||||
* Class to define a join rule state event.
|
||||
*
|
||||
* @sa Quotient::StateEvent
|
||||
*/
|
||||
#ifdef QUOTIENT_07
|
||||
class JoinRulesEvent : public StateEvent
|
||||
#else
|
||||
@@ -36,20 +29,7 @@ public:
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The join rule for the room.
|
||||
*
|
||||
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
|
||||
* the available join rules for a room.
|
||||
*/
|
||||
QString joinRule() const;
|
||||
|
||||
/**
|
||||
* @brief The allow rule for restricted rooms.
|
||||
*
|
||||
* see https://spec.matrix.org/latest/client-server-api/#mroomjoin_rules for
|
||||
* full details on allow rules.
|
||||
*/
|
||||
QJsonArray allow() const;
|
||||
};
|
||||
REGISTER_EVENT_TYPE(JoinRulesEvent)
|
||||
@@ -3,20 +3,17 @@
|
||||
|
||||
#include "linkpreviewer.h"
|
||||
|
||||
#include "controller.h"
|
||||
|
||||
#include <connection.h>
|
||||
#include <csapi/content-repo.h>
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
LinkPreviewer::LinkPreviewer(QObject *parent, NeoChatRoom *room, QUrl url)
|
||||
LinkPreviewer::LinkPreviewer(QObject *parent)
|
||||
: QObject(parent)
|
||||
, m_currentRoom(room)
|
||||
, m_loaded(false)
|
||||
, m_url(url)
|
||||
{
|
||||
loadUrlPreview();
|
||||
}
|
||||
|
||||
bool LinkPreviewer::loaded() const
|
||||
@@ -34,7 +31,7 @@ QString LinkPreviewer::description() const
|
||||
return m_description;
|
||||
}
|
||||
|
||||
QUrl LinkPreviewer::imageSource() const
|
||||
QString LinkPreviewer::imageSource() const
|
||||
{
|
||||
return m_imageSource;
|
||||
}
|
||||
@@ -46,41 +43,22 @@ QUrl LinkPreviewer::url() const
|
||||
|
||||
void LinkPreviewer::setUrl(QUrl url)
|
||||
{
|
||||
if (url != m_url) {
|
||||
m_url = url;
|
||||
urlChanged();
|
||||
loadUrlPreview();
|
||||
}
|
||||
}
|
||||
|
||||
void LinkPreviewer::loadUrlPreview()
|
||||
{
|
||||
if (m_url.scheme() == QStringLiteral("https")) {
|
||||
if (url.scheme() == QStringLiteral("https")) {
|
||||
m_loaded = false;
|
||||
Q_EMIT loadedChanged();
|
||||
|
||||
auto conn = m_currentRoom->connection();
|
||||
m_url = url;
|
||||
Q_EMIT urlChanged();
|
||||
|
||||
auto conn = Controller::instance().activeConnection();
|
||||
|
||||
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url.toString());
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job, conn]() {
|
||||
connect(job, &BaseJob::success, this, [this, job]() {
|
||||
const auto json = job->jsonData();
|
||||
m_title = json["og:title"].toString().trimmed();
|
||||
m_description = json["og:description"].toString().trimmed().replace("\n", " ");
|
||||
|
||||
auto imageUrl = QUrl(json["og:image"].toString());
|
||||
if (imageUrl.isValid() && imageUrl.scheme() == QStringLiteral("mxc")) {
|
||||
#ifdef QUOTIENT_07
|
||||
m_imageSource = conn->makeMediaUrl(imageUrl);
|
||||
#else
|
||||
QUrlQuery q(imageUrl.query());
|
||||
q.addQueryItem(QStringLiteral("user_id"), conn->userId());
|
||||
imageUrl.setQuery(q);
|
||||
m_imageSource = imageUrl;
|
||||
#endif
|
||||
} else {
|
||||
m_imageSource = QUrl();
|
||||
}
|
||||
|
||||
m_imageSource = json["og:image"].toString();
|
||||
m_loaded = true;
|
||||
Q_EMIT titleChanged();
|
||||
Q_EMIT descriptionChanged();
|
||||
|
||||
@@ -6,65 +6,33 @@
|
||||
#include <QObject>
|
||||
#include <QUrl>
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class LinkPreviewer
|
||||
*
|
||||
* A class to download the link preview info for a URL and provide a QML interface for it.
|
||||
*
|
||||
* To use set the URL property and then access the other parameters which will be
|
||||
* populated once loaded is true.
|
||||
*/
|
||||
class LinkPreviewer : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
/**
|
||||
* @brief The URL to get the preview for.
|
||||
*/
|
||||
Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
|
||||
Q_PROPERTY(QString imageSource READ imageSource NOTIFY imageSourceChanged)
|
||||
Q_PROPERTY(QUrl url READ url WRITE setUrl NOTIFY urlChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the preview information has been loaded.
|
||||
*/
|
||||
Q_PROPERTY(bool loaded READ loaded NOTIFY loadedChanged)
|
||||
|
||||
/**
|
||||
* @brief The title of the preview.
|
||||
*/
|
||||
Q_PROPERTY(QString title READ title NOTIFY titleChanged)
|
||||
|
||||
/**
|
||||
* @brief The description of the preview.
|
||||
*/
|
||||
Q_PROPERTY(QString description READ description NOTIFY descriptionChanged)
|
||||
|
||||
/**
|
||||
* @brief The image source for the preview.
|
||||
*/
|
||||
Q_PROPERTY(QUrl imageSource READ imageSource NOTIFY imageSourceChanged)
|
||||
|
||||
public:
|
||||
explicit LinkPreviewer(QObject *parent = nullptr, NeoChatRoom *room = nullptr, QUrl url = {});
|
||||
explicit LinkPreviewer(QObject *parent = 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]] QString imageSource() const;
|
||||
[[nodiscard]] QUrl url() const;
|
||||
|
||||
void setUrl(QUrl);
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_currentRoom = nullptr;
|
||||
|
||||
bool m_loaded;
|
||||
QString m_title = QString();
|
||||
QString m_description = QString();
|
||||
QUrl m_imageSource = QUrl();
|
||||
QString m_title;
|
||||
QString m_description;
|
||||
QString m_imageSource;
|
||||
QUrl m_url;
|
||||
|
||||
void loadUrlPreview();
|
||||
|
||||
Q_SIGNALS:
|
||||
void loadedChanged();
|
||||
void titleChanged();
|
||||
@@ -72,4 +40,3 @@ Q_SIGNALS:
|
||||
void imageSourceChanged();
|
||||
void urlChanged();
|
||||
};
|
||||
Q_DECLARE_METATYPE(LinkPreviewer *)
|
||||
|
||||
208
src/logger.cpp
208
src/logger.cpp
@@ -1,208 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 1997 Matthias Kalle Dalheimer <kalle@kde.org>
|
||||
// SPDX-FileCopyrightText: 2002 Holger Freyther <freyther@kde.org>
|
||||
// SPDX-FileCopyrightText: 2008 Volker Krause <vkrause@kde.org>
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <fella@posteo.de>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "logger.h"
|
||||
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QFileInfo>
|
||||
#include <QLoggingCategory>
|
||||
#include <QMutex>
|
||||
#include <QStandardPaths>
|
||||
|
||||
static QLoggingCategory::CategoryFilter oldCategoryFilter = nullptr;
|
||||
static QtMessageHandler oldHandler = nullptr;
|
||||
static bool e2eeDebugEnabled = false;
|
||||
|
||||
class FileDebugStream : public QIODevice
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
FileDebugStream()
|
||||
: mType(QtCriticalMsg)
|
||||
{
|
||||
open(WriteOnly);
|
||||
}
|
||||
|
||||
bool isSequential() const override
|
||||
{
|
||||
return true;
|
||||
}
|
||||
qint64 readData(char *, qint64) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
qint64 readLineData(char *, qint64) override
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
qint64 writeData(const char *data, qint64 len) override
|
||||
{
|
||||
if (!mFileName.isEmpty()) {
|
||||
QFile outputFile(mFileName);
|
||||
outputFile.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Unbuffered);
|
||||
outputFile.write(data, len);
|
||||
outputFile.putChar('\n');
|
||||
outputFile.close();
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
void setFileName(const QString &fileName)
|
||||
{
|
||||
mFileName = fileName;
|
||||
}
|
||||
|
||||
void setType(QtMsgType type)
|
||||
{
|
||||
mType = type;
|
||||
}
|
||||
|
||||
private:
|
||||
QString mFileName;
|
||||
QtMsgType mType;
|
||||
};
|
||||
|
||||
class DebugPrivate
|
||||
{
|
||||
public:
|
||||
DebugPrivate()
|
||||
: origHandler(nullptr)
|
||||
{
|
||||
}
|
||||
|
||||
~DebugPrivate()
|
||||
{
|
||||
qInstallMessageHandler(origHandler);
|
||||
file.close();
|
||||
}
|
||||
|
||||
void log(QtMsgType type, const QMessageLogContext &context, const QString &message)
|
||||
{
|
||||
QMutexLocker locker(&mutex);
|
||||
QByteArray buf;
|
||||
QTextStream str(&buf);
|
||||
str << QDateTime::currentDateTime().toString(Qt::ISODate) << " [";
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
str << "DEBUG";
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
str << "INFO ";
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
str << "WARN ";
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
str << "FATAL";
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
str << "CRITICAL";
|
||||
break;
|
||||
}
|
||||
str << "] " << context.category << ": ";
|
||||
if (context.file && *context.file && context.line) {
|
||||
str << context.file << ":" << context.line << ": ";
|
||||
}
|
||||
if (context.function && *context.function) {
|
||||
str << context.function << ": ";
|
||||
}
|
||||
str << message << "\n";
|
||||
str.flush();
|
||||
file.write(buf.constData(), buf.size());
|
||||
file.flush();
|
||||
|
||||
if (oldHandler && (strcmp(context.category, "quotient.e2ee") != 0 || e2eeDebugEnabled)) {
|
||||
oldHandler(type, context, message);
|
||||
}
|
||||
}
|
||||
|
||||
void setName(const QString &appName)
|
||||
{
|
||||
name = appName;
|
||||
|
||||
if (file.isOpen()) {
|
||||
file.close();
|
||||
}
|
||||
auto filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + appName;
|
||||
|
||||
QFileInfo infoOld(filePath + QLatin1String(".old"));
|
||||
if (infoOld.exists()) {
|
||||
QFile fileOld(infoOld.absoluteFilePath());
|
||||
const bool success = fileOld.remove();
|
||||
if (!success) {
|
||||
qFatal("Cannot remove old log file '%s': %s", qUtf8Printable(fileOld.fileName()), qUtf8Printable(fileOld.errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
QFileInfo info(filePath);
|
||||
if (info.exists()) {
|
||||
QFile file(info.absoluteFilePath());
|
||||
const QString oldName = filePath + QLatin1String(".old");
|
||||
const bool success = file.copy(oldName);
|
||||
if (!success) {
|
||||
qFatal("Cannot rename log file '%s' to '%s': %s", qUtf8Printable(file.fileName()), qUtf8Printable(oldName), qUtf8Printable(file.errorString()));
|
||||
}
|
||||
}
|
||||
|
||||
QFileInfo finfo(filePath);
|
||||
if (!finfo.absoluteDir().exists()) {
|
||||
QDir().mkpath(finfo.absolutePath());
|
||||
}
|
||||
file.setFileName(filePath);
|
||||
file.open(QIODevice::WriteOnly | QIODevice::Unbuffered);
|
||||
}
|
||||
|
||||
void setOrigHandler(QtMessageHandler origHandler_)
|
||||
{
|
||||
origHandler = origHandler_;
|
||||
}
|
||||
|
||||
QMutex mutex;
|
||||
QFile file;
|
||||
QString name;
|
||||
QtMessageHandler origHandler;
|
||||
QByteArray loggingCategory;
|
||||
};
|
||||
|
||||
Q_GLOBAL_STATIC(DebugPrivate, sInstance)
|
||||
|
||||
void messageHandler(QtMsgType type, const QMessageLogContext &context, const QString &message)
|
||||
{
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
case QtInfoMsg:
|
||||
case QtWarningMsg:
|
||||
case QtCriticalMsg:
|
||||
sInstance()->log(type, context, message);
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
sInstance()->log(QtInfoMsg, context, message);
|
||||
abort();
|
||||
}
|
||||
}
|
||||
|
||||
void filter(QLoggingCategory *category)
|
||||
{
|
||||
if (qstrcmp(category->categoryName(), "quotient.e2ee") == 0) {
|
||||
category->setEnabled(QtDebugMsg, true);
|
||||
} else if (oldCategoryFilter) {
|
||||
oldCategoryFilter(category);
|
||||
}
|
||||
}
|
||||
|
||||
void initLogging()
|
||||
{
|
||||
e2eeDebugEnabled = QLoggingCategory("quotient.e2ee", QtDebugMsg).isEnabled(QtDebugMsg);
|
||||
oldCategoryFilter = QLoggingCategory::installFilter(filter);
|
||||
oldHandler = qInstallMessageHandler(messageHandler);
|
||||
sInstance->setOrigHandler(oldHandler);
|
||||
sInstance->setName("neochat.log");
|
||||
}
|
||||
|
||||
#include "logger.moc"
|
||||
@@ -1,9 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
/**
|
||||
* Initlalize logging to file and enables some additional categories, which will only be logged to the file
|
||||
*/
|
||||
void initLogging();
|
||||
48
src/login.h
48
src/login.h
@@ -10,67 +10,19 @@ namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class Login
|
||||
*
|
||||
* A helper class for logging into a Matrix account.
|
||||
*/
|
||||
class Login : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief Whether the home server for the account is reachable.
|
||||
*/
|
||||
Q_PROPERTY(bool homeserverReachable READ homeserverReachable NOTIFY homeserverReachableChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the connection to the home server is being tested.
|
||||
*
|
||||
* True if NeoChat is trying to resolve the home server, false if not started
|
||||
* or complete.
|
||||
*/
|
||||
Q_PROPERTY(bool testing READ testing NOTIFY testingChanged)
|
||||
|
||||
/**
|
||||
* @brief The Matrix ID of the account that is being logged into.
|
||||
*/
|
||||
Q_PROPERTY(QString matrixId READ matrixId WRITE setMatrixId NOTIFY matrixIdChanged)
|
||||
|
||||
/**
|
||||
* @brief The password entered by the user to login to the account.
|
||||
*/
|
||||
Q_PROPERTY(QString password READ password WRITE setPassword NOTIFY passwordChanged)
|
||||
|
||||
/**
|
||||
* @brief The device name to assign to this session.
|
||||
*/
|
||||
Q_PROPERTY(QString deviceName READ deviceName WRITE setDeviceName NOTIFY deviceNameChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the home server of the account supports single sign on login.
|
||||
*/
|
||||
Q_PROPERTY(bool supportsSso READ supportsSso NOTIFY loginFlowsChanged STORED false)
|
||||
|
||||
/**
|
||||
* @brief Whether the home server of the account supports password login.
|
||||
*/
|
||||
Q_PROPERTY(bool supportsPassword READ supportsPassword NOTIFY loginFlowsChanged STORED false)
|
||||
|
||||
/**
|
||||
* @brief The URL for the single sign on session.
|
||||
*/
|
||||
Q_PROPERTY(QUrl ssoUrl READ ssoUrl NOTIFY ssoUrlChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether login process is ongoing.
|
||||
*/
|
||||
Q_PROPERTY(bool isLoggingIn READ isLoggingIn NOTIFY isLoggingInChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether login has successfully completed.
|
||||
*/
|
||||
Q_PROPERTY(bool isLoggedIn READ isLoggedIn NOTIFY isLoggedInChanged)
|
||||
|
||||
public:
|
||||
|
||||
10
src/main.cpp
10
src/main.cpp
@@ -45,14 +45,12 @@
|
||||
#include "controller.h"
|
||||
#include "filetypesingleton.h"
|
||||
#include "linkpreviewer.h"
|
||||
#include "logger.h"
|
||||
#include "login.h"
|
||||
#include "matriximageprovider.h"
|
||||
#include "models/collapsestateproxymodel.h"
|
||||
#include "models/customemojimodel.h"
|
||||
#include "models/devicesmodel.h"
|
||||
#include "models/emojimodel.h"
|
||||
#include "models/imagepacksmodel.h"
|
||||
#include "models/keywordnotificationrulemodel.h"
|
||||
#include "models/messageeventmodel.h"
|
||||
#include "models/messagefiltermodel.h"
|
||||
@@ -73,8 +71,6 @@
|
||||
#ifdef QUOTIENT_07
|
||||
#include "pollhandler.h"
|
||||
#endif
|
||||
#include "models/statefiltermodel.h"
|
||||
#include "models/stickermodel.h"
|
||||
#include "roommanager.h"
|
||||
#include "spacehierarchycache.h"
|
||||
#include "urlhelper.h"
|
||||
@@ -183,8 +179,6 @@ int main(int argc, char *argv[])
|
||||
KAboutData::setApplicationData(about);
|
||||
QGuiApplication::setWindowIcon(QIcon::fromTheme(QStringLiteral("org.kde.neochat")));
|
||||
|
||||
initLogging();
|
||||
|
||||
#ifdef NEOCHAT_FLATPAK
|
||||
// Copy over the included FontConfig configuration to the
|
||||
// app's config dir:
|
||||
@@ -240,18 +234,16 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
|
||||
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
||||
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
||||
qmlRegisterType<StateFilterModel>("org.kde.neochat", 1, 0, "StateFilterModel");
|
||||
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
||||
#ifdef QUOTIENT_07
|
||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||
#endif
|
||||
qmlRegisterType<KeywordNotificationRuleModel>("org.kde.neochat", 1, 0, "KeywordNotificationRuleModel");
|
||||
qmlRegisterType<StickerModel>("org.kde.neochat", 1, 0, "StickerModel");
|
||||
qmlRegisterType<ImagePacksModel>("org.kde.neochat", 1, 0, "ImagePacksModel");
|
||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
||||
qmlRegisterUncreatableType<UserType>("org.kde.neochat", 1, 0, "UserType", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatUser>("org.kde.neochat", 1, 0, "NeoChatUser", {});
|
||||
|
||||
qRegisterMetaType<User *>("User*");
|
||||
|
||||
@@ -98,6 +98,18 @@ void ThumbnailResponse::prepareResult()
|
||||
Q_EMIT finished();
|
||||
}
|
||||
|
||||
void ThumbnailResponse::doCancel()
|
||||
{
|
||||
if (!Controller::instance().activeConnection()) {
|
||||
return;
|
||||
}
|
||||
// Runs in the main thread, not QML thread
|
||||
if (job) {
|
||||
Q_ASSERT(QThread::currentThread() == job->thread());
|
||||
job->abandon();
|
||||
}
|
||||
}
|
||||
|
||||
QQuickTextureFactory *ThumbnailResponse::textureFactory() const
|
||||
{
|
||||
QReadLocker _(&lock);
|
||||
@@ -110,6 +122,11 @@ QString ThumbnailResponse::errorString() const
|
||||
return errorStr;
|
||||
}
|
||||
|
||||
void ThumbnailResponse::cancel()
|
||||
{
|
||||
QMetaObject::invokeMethod(this, &ThumbnailResponse::doCancel, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
QQuickImageResponse *MatrixImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||
{
|
||||
return new ThumbnailResponse(id, requestedSize);
|
||||
|
||||
@@ -15,13 +15,6 @@ namespace Quotient
|
||||
class Connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class ThumbnailResponse
|
||||
*
|
||||
* A QQuickImageResponse for an mxc image.
|
||||
*
|
||||
* @sa QQuickImageResponse
|
||||
*/
|
||||
class ThumbnailResponse : public QQuickImageResponse
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -32,6 +25,7 @@ public:
|
||||
private Q_SLOTS:
|
||||
void startRequest();
|
||||
void prepareResult();
|
||||
void doCancel();
|
||||
|
||||
private:
|
||||
const QString mediaId;
|
||||
@@ -45,22 +39,11 @@ private:
|
||||
|
||||
QQuickTextureFactory *textureFactory() const override;
|
||||
QString errorString() const override;
|
||||
void cancel() override;
|
||||
};
|
||||
|
||||
/**
|
||||
* @class MatrixImageProvider
|
||||
*
|
||||
* A QQuickAsyncImageProvider for mxc images.
|
||||
*
|
||||
* @sa QQuickAsyncImageProvider
|
||||
*/
|
||||
class MatrixImageProvider : public QQuickAsyncImageProvider
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @brief Return a job to provide the image with the given ID.
|
||||
*
|
||||
* @sa QQuickAsyncImageProvider::requestImageResponse
|
||||
*/
|
||||
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
|
||||
};
|
||||
|
||||
@@ -24,8 +24,6 @@ QVariant CollapseStateProxyModel::data(const QModelIndex &index, int role) const
|
||||
return stateEventsList(mapToSource(index).row());
|
||||
} else if (role == AuthorListRole) {
|
||||
return authorList(mapToSource(index).row());
|
||||
} else if (role == ExcessAuthorsRole) {
|
||||
return excessAuthors(mapToSource(index).row());
|
||||
}
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
}
|
||||
@@ -36,7 +34,6 @@ QHash<int, QByteArray> CollapseStateProxyModel::roleNames() const
|
||||
roles[AggregateDisplayRole] = "aggregateDisplay";
|
||||
roles[StateEventsRole] = "stateEvents";
|
||||
roles[AuthorListRole] = "authorList";
|
||||
roles[ExcessAuthorsRole] = "excessAuthors";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -133,39 +130,5 @@ QVariantList CollapseStateProxyModel::authorList(int sourceRow) const
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
uniqueAuthors = uniqueAuthors.mid(0, 5);
|
||||
}
|
||||
return uniqueAuthors;
|
||||
}
|
||||
|
||||
QString CollapseStateProxyModel::excessAuthors(int row) const
|
||||
{
|
||||
QVariantList uniqueAuthors;
|
||||
for (int i = row; i >= 0; i--) {
|
||||
QVariant nextAvatar = sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole);
|
||||
if (!uniqueAuthors.contains(nextAvatar)) {
|
||||
uniqueAuthors.append(nextAvatar);
|
||||
}
|
||||
if (sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::DelegateTypeRole)
|
||||
!= MessageEventModel::DelegateType::State // If it's not a state event
|
||||
|| sourceModel()->data(sourceModel()->index(i - 1, 0), MessageEventModel::ShowSectionRole).toBool() // or the section needs to be visible
|
||||
) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int excessAuthors;
|
||||
if (uniqueAuthors.count() > 5) {
|
||||
excessAuthors = uniqueAuthors.count() - 5;
|
||||
} else {
|
||||
excessAuthors = 0;
|
||||
}
|
||||
QString excessAuthorsString;
|
||||
if (excessAuthors == 0) {
|
||||
return QString();
|
||||
} else {
|
||||
return QStringLiteral("+ %1").arg(excessAuthors);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,8 +25,7 @@ public:
|
||||
enum Roles {
|
||||
AggregateDisplayRole = MessageEventModel::LastRole + 1, /**< Single line aggregation of all the state events. */
|
||||
StateEventsRole, /**< List of state events in the aggregated state. */
|
||||
AuthorListRole, /**< List of the first 5 unique authors of the aggregated state event. */
|
||||
ExcessAuthorsRole, /**< The number of unique authors beyond the first 5. */
|
||||
AuthorListRole, /**< List of unique authors of the aggregated state event. */
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -68,12 +67,7 @@ private:
|
||||
[[nodiscard]] QVariantList stateEventsList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief List of the first 5 unique authors for the aggregate state events starting at row.
|
||||
* @brief List of unique authors for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QVariantList authorList(int row) const;
|
||||
|
||||
/**
|
||||
* @brief The number of unique authors beyond the first 5 for the aggregate state events starting at row.
|
||||
*/
|
||||
[[nodiscard]] QString excessAuthors(int row) const;
|
||||
};
|
||||
|
||||
@@ -2,17 +2,12 @@
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "customemojimodel.h"
|
||||
|
||||
#include <QImage>
|
||||
#include <QMimeDatabase>
|
||||
|
||||
#include "controller.h"
|
||||
#include "emojimodel.h"
|
||||
|
||||
#include <connection.h>
|
||||
#include <csapi/account-data.h>
|
||||
#include <csapi/content-repo.h>
|
||||
#include <events/eventcontent.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -63,31 +58,17 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
|
||||
|
||||
if (running(job)) {
|
||||
connect(job, &BaseJob::success, this, [name, location, job] {
|
||||
connect(job, &BaseJob::success, this, [name, job] {
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes");
|
||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||
auto emojiData = json["images"].toObject();
|
||||
|
||||
QString url;
|
||||
#ifdef QUOTIENT_07
|
||||
url = job->contentUri().toString();
|
||||
#else
|
||||
url = job->contentUri();
|
||||
#endif
|
||||
|
||||
QImage image(location.toLocalFile());
|
||||
QJsonObject imageInfo;
|
||||
imageInfo["w"] = image.width();
|
||||
imageInfo["h"] = image.height();
|
||||
imageInfo["mimetype"] = QMimeDatabase().mimeTypeForFile(location.toLocalFile()).name();
|
||||
imageInfo["size"] = image.sizeInBytes();
|
||||
|
||||
emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({
|
||||
{QStringLiteral("url"), url},
|
||||
{QStringLiteral("info"), imageInfo},
|
||||
{QStringLiteral("body"), location.fileName()},
|
||||
#ifdef QUOTIENT_07
|
||||
{QStringLiteral("url"), job->contentUri().toString()}
|
||||
#else
|
||||
{QStringLiteral("url"), job->contentUri()}
|
||||
#endif
|
||||
});
|
||||
|
||||
json["images"] = emojiData;
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes", json);
|
||||
});
|
||||
|
||||
@@ -1,144 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "imagepacksmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
ImagePacksModel::ImagePacksModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int ImagePacksModel::rowCount(const QModelIndex &index) const
|
||||
{
|
||||
return m_events.count();
|
||||
}
|
||||
|
||||
QVariant ImagePacksModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
const auto &event = m_events[index.row()];
|
||||
if (role == DisplayNameRole) {
|
||||
if (event.pack->displayName) {
|
||||
return *event.pack->displayName;
|
||||
}
|
||||
}
|
||||
if (role == AvatarUrlRole) {
|
||||
if (event.pack->avatarUrl) {
|
||||
#ifdef QUOTIENT_07
|
||||
return m_room->connection()->makeMediaUrl(*event.pack->avatarUrl);
|
||||
#endif
|
||||
} else if (!event.images.empty()) {
|
||||
#ifdef QUOTIENT_07
|
||||
return m_room->connection()->makeMediaUrl(event.images[0].url);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ImagePacksModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{DisplayNameRole, "displayName"},
|
||||
{AvatarUrlRole, "avatarUrl"},
|
||||
{AttributionRole, "attribution"},
|
||||
{IdRole, "id"},
|
||||
};
|
||||
}
|
||||
|
||||
NeoChatRoom *ImagePacksModel::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room) {
|
||||
disconnect(m_room, nullptr, this, nullptr);
|
||||
}
|
||||
m_room = room;
|
||||
|
||||
beginResetModel();
|
||||
m_events.clear();
|
||||
|
||||
// TODO listen to account data changing
|
||||
// TODO listen to packs changing
|
||||
if (m_room->connection()->hasAccountData("im.ponies.user_emotes"_ls)) {
|
||||
auto json = m_room->connection()->accountData("im.ponies.user_emotes"_ls)->contentJson();
|
||||
json["pack"] = QJsonObject{
|
||||
{"display_name", i18n("Own Stickers")},
|
||||
};
|
||||
const auto &content = ImagePackEventContent(json);
|
||||
if (!content.images.isEmpty()) {
|
||||
m_events += ImagePackEventContent(json);
|
||||
}
|
||||
}
|
||||
const auto &accountData = m_room->connection()->accountData("im.ponies.emote_rooms"_ls);
|
||||
if (accountData) {
|
||||
const auto &rooms = accountData->contentJson()["rooms"_ls].toObject();
|
||||
for (const auto &roomId : rooms.keys()) {
|
||||
if (roomId == m_room->id()) {
|
||||
continue;
|
||||
}
|
||||
auto packs = rooms[roomId].toObject();
|
||||
const auto &stickerRoom = m_room->connection()->room(roomId);
|
||||
for (const auto &packKey : packs.keys()) {
|
||||
#ifdef QUOTIENT_07
|
||||
const auto &pack = stickerRoom->currentState().get<ImagePackEvent>(packKey);
|
||||
if (pack) {
|
||||
const auto packContent = pack->content();
|
||||
if (!packContent.pack->usage || (packContent.pack->usage->contains("emoticon") && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker") && showStickers())) {
|
||||
m_events += packContent;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
#ifdef QUOTIENT_07
|
||||
auto events = m_room->currentState().eventsOfType("im.ponies.room_emotes");
|
||||
for (const auto &event : events) {
|
||||
auto packContent = eventCast<const ImagePackEvent>(event)->content();
|
||||
if (!packContent.pack->usage || (packContent.pack->usage->contains("emoticon") && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker") && showStickers())) {
|
||||
m_events += packContent;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
endResetModel();
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
bool ImagePacksModel::showStickers() const
|
||||
{
|
||||
return m_showStickers;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setShowStickers(bool showStickers)
|
||||
{
|
||||
m_showStickers = showStickers;
|
||||
Q_EMIT showStickersChanged();
|
||||
}
|
||||
|
||||
bool ImagePacksModel::showEmoticons() const
|
||||
{
|
||||
return m_showEmoticons;
|
||||
}
|
||||
|
||||
void ImagePacksModel::setShowEmoticons(bool showEmoticons)
|
||||
{
|
||||
m_showEmoticons = showEmoticons;
|
||||
Q_EMIT showEmoticonsChanged();
|
||||
}
|
||||
QVector<Quotient::ImagePackEventContent::ImagePackImage> ImagePacksModel::images(int index)
|
||||
{
|
||||
if (index < 0 || index >= m_events.size()) {
|
||||
return {};
|
||||
}
|
||||
return m_events[index].images;
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "events/imagepackevent.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QPointer>
|
||||
#include <QVector>
|
||||
|
||||
class NeoChatRoom;
|
||||
|
||||
/**
|
||||
* @class ImagePacksModel
|
||||
*
|
||||
* Defines the model for visualising image packs.
|
||||
*
|
||||
* See Matrix MSC2545 for more details on image packs.
|
||||
* https://github.com/Sorunome/matrix-doc/blob/soru/emotes/proposals/2545-emotes.md
|
||||
*/
|
||||
class ImagePacksModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is being used in.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether sticker image packs should be shown.
|
||||
*/
|
||||
Q_PROPERTY(bool showStickers READ showStickers WRITE setShowStickers NOTIFY showStickersChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether emoticon image packs should be shown.
|
||||
*/
|
||||
Q_PROPERTY(bool showEmoticons READ showEmoticons WRITE setShowEmoticons NOTIFY showEmoticonsChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
DisplayNameRole = Qt::DisplayRole, /**< The display name of the image pack. */
|
||||
AvatarUrlRole, /**< The source mxc URL for the pack avatar. */
|
||||
AttributionRole, /**< The attribution for the pack author(s). */
|
||||
IdRole, /**< The ID of the image pack. */
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
explicit ImagePacksModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &index) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
[[nodiscard]] bool showStickers() const;
|
||||
void setShowStickers(bool showStickers);
|
||||
|
||||
[[nodiscard]] bool showEmoticons() const;
|
||||
void setShowEmoticons(bool showEmoticons);
|
||||
|
||||
/**
|
||||
* @brief Return a vector of the images in the pack at the given index.
|
||||
*/
|
||||
[[nodiscard]] QVector<Quotient::ImagePackEventContent::ImagePackImage> images(int index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void showStickersChanged();
|
||||
void showEmoticonsChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_room;
|
||||
QVector<Quotient::ImagePackEventContent> m_events;
|
||||
bool m_showStickers = true;
|
||||
bool m_showEmoticons = true;
|
||||
};
|
||||
@@ -2,7 +2,6 @@
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "messageeventmodel_logging.h"
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include <connection.h>
|
||||
@@ -15,9 +14,9 @@
|
||||
#include <user.h>
|
||||
|
||||
#ifdef QUOTIENT_07
|
||||
#include "events/pollevent.h"
|
||||
#include "pollevent.h"
|
||||
#endif
|
||||
#include "events/stickerevent.h"
|
||||
#include "stickerevent.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
@@ -28,7 +27,6 @@
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "neochatuser.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -36,28 +34,24 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
|
||||
roles[DelegateTypeRole] = "delegateType";
|
||||
roles[PlainText] = "plainText";
|
||||
roles[MessageRole] = "message";
|
||||
roles[EventIdRole] = "eventId";
|
||||
roles[TimeRole] = "time";
|
||||
roles[SectionRole] = "section";
|
||||
roles[AuthorRole] = "author";
|
||||
roles[ContentRole] = "content";
|
||||
roles[ContentTypeRole] = "contentType";
|
||||
roles[HighlightRole] = "isHighlighted";
|
||||
roles[SpecialMarksRole] = "marks";
|
||||
roles[LongOperationRole] = "progressInfo";
|
||||
roles[FileMimetypeIcon] = "fileMimetypeIcon";
|
||||
roles[EventResolvedTypeRole] = "eventResolvedType";
|
||||
roles[ShowLinkPreviewRole] = "showLinkPreview";
|
||||
roles[LinkPreviewRole] = "linkPreview";
|
||||
roles[MediaInfoRole] = "mediaInfo";
|
||||
roles[IsReplyRole] = "isReply";
|
||||
roles[ReplyAuthor] = "replyAuthor";
|
||||
roles[ReplyRole] = "reply";
|
||||
roles[ReplyIdRole] = "replyId";
|
||||
roles[ReplyMediaInfoRole] = "replyMediaInfo";
|
||||
roles[ShowAuthorRole] = "showAuthor";
|
||||
roles[ShowSectionRole] = "showSection";
|
||||
roles[ReadMarkersRole] = "readMarkers";
|
||||
roles[ExcessReadMarkersRole] = "excessReadMarkers";
|
||||
roles[ReadMarkersStringRole] = "readMarkersString";
|
||||
roles[ShowReadMarkersRole] = "showReadMarkers";
|
||||
roles[ReactionRole] = "reaction";
|
||||
@@ -65,6 +59,7 @@ QHash<int, QByteArray> MessageEventModel::roleNames() const
|
||||
roles[MimeTypeRole] = "mimeType";
|
||||
roles[FormattedBodyRole] = "formattedBody";
|
||||
roles[AuthorIdRole] = "authorId";
|
||||
roles[MediaUrlRole] = "mediaUrl";
|
||||
roles[VerifiedRole] = "verified";
|
||||
roles[DisplayNameForInitialsRole] = "displayNameForInitials";
|
||||
roles[AuthorDisplayNameRole] = "authorDisplayName";
|
||||
@@ -105,20 +100,12 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
beginResetModel();
|
||||
if (m_currentRoom) {
|
||||
m_currentRoom->disconnect(this);
|
||||
m_linkPreviewers.clear();
|
||||
}
|
||||
|
||||
m_currentRoom = room;
|
||||
if (room) {
|
||||
m_lastReadEventIndex = QPersistentModelIndex(QModelIndex());
|
||||
room->setDisplayed();
|
||||
|
||||
for (auto event = m_currentRoom->messageEvents().begin(); event != m_currentRoom->messageEvents().end(); ++event) {
|
||||
if (auto e = &*event->viewAs<RoomMessageEvent>()) {
|
||||
createLinkPreviewerForEvent(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (m_currentRoom->timelineSize() < 10 && !room->allHistoryLoaded()) {
|
||||
room->getPreviousContent(50);
|
||||
}
|
||||
@@ -130,12 +117,10 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
|
||||
using namespace Quotient;
|
||||
connect(m_currentRoom, &Room::aboutToAddNewMessages, this, [this](RoomEventsRange events) {
|
||||
for (auto &&event : events) {
|
||||
const RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||
if (message != nullptr) {
|
||||
createLinkPreviewerForEvent(message);
|
||||
|
||||
if (NeoChatConfig::self()->showFancyEffects()) {
|
||||
if (NeoChatConfig::self()->showFancyEffects()) {
|
||||
for (auto &event : events) {
|
||||
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||
if (message) {
|
||||
QString planBody = message->plainBody();
|
||||
// snowflake
|
||||
const QString snowlakeEmoji = QString::fromUtf8("\xE2\x9D\x84");
|
||||
@@ -165,26 +150,16 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
}
|
||||
}
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, timelineBaseIndex(), timelineBaseIndex() + int(events.size()) - 1);
|
||||
});
|
||||
connect(m_currentRoom, &Room::aboutToAddHistoricalMessages, this, [this](RoomEventsRange events) {
|
||||
for (auto &event : events) {
|
||||
RoomMessageEvent *message = dynamic_cast<RoomMessageEvent *>(event.get());
|
||||
if (message) {
|
||||
createLinkPreviewerForEvent(message);
|
||||
}
|
||||
}
|
||||
if (rowCount() > 0) {
|
||||
rowBelowInserted = rowCount() - 1; // See #312
|
||||
}
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, rowCount(), rowCount() + int(events.size()) - 1);
|
||||
});
|
||||
connect(m_currentRoom, &Room::addedMessages, this, [this](int lowest, int biggest) {
|
||||
if (m_initialized) {
|
||||
endInsertRows();
|
||||
}
|
||||
endInsertRows();
|
||||
if (!m_lastReadEventIndex.isValid()) {
|
||||
// no read marker, so see if we need to create one.
|
||||
#ifdef QUOTIENT_07
|
||||
@@ -202,7 +177,6 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
}
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAboutToAdd, this, [this] {
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, 0, 0);
|
||||
});
|
||||
connect(m_currentRoom, &Room::pendingEventAdded, this, &MessageEventModel::endInsertRows);
|
||||
@@ -249,7 +223,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
connect(m_currentRoom, &Room::changed, this, [this]() {
|
||||
for (auto it = m_currentRoom->messageEvents().rbegin(); it != m_currentRoom->messageEvents().rend(); ++it) {
|
||||
auto event = it->event();
|
||||
refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole, ExcessReadMarkersRole});
|
||||
refreshEventRoles(event->id(), {ReadMarkersRole, ReadMarkersStringRole});
|
||||
}
|
||||
});
|
||||
connect(m_currentRoom, &Room::newFileTransfer, this, &MessageEventModel::refreshEvent);
|
||||
@@ -263,7 +237,7 @@ void MessageEventModel::setRoom(NeoChatRoom *room)
|
||||
beginResetModel();
|
||||
endResetModel();
|
||||
});
|
||||
qCDebug(MessageEvent) << "Connected to room" << room->id() << "as" << room->localUser()->id();
|
||||
qDebug() << "Connected to room" << room->id() << "as" << room->localUser()->id();
|
||||
} else {
|
||||
lastReadEventId.clear();
|
||||
}
|
||||
@@ -305,7 +279,6 @@ void MessageEventModel::moveReadMarker(const QString &toEventId)
|
||||
// need to be displayed.
|
||||
if (newRow > timelineBaseIndex()) {
|
||||
// The user didn't read all the messages yet.
|
||||
m_initialized = true;
|
||||
beginInsertRows({}, newRow, newRow);
|
||||
m_lastReadEventIndex = QPersistentModelIndex(index(newRow, 0));
|
||||
endInsertRows();
|
||||
@@ -341,6 +314,7 @@ int MessageEventModel::refreshEventRoles(const QString &id, const QVector<int> &
|
||||
} else {
|
||||
const auto timelineIt = m_currentRoom->findInTimeline(id);
|
||||
if (timelineIt == m_currentRoom->historyEdge()) {
|
||||
qWarning() << "Trying to refresh inexistent event:" << id;
|
||||
return -1;
|
||||
}
|
||||
row = int(timelineIt - m_currentRoom->messageEvents().rbegin()) + timelineBaseIndex();
|
||||
@@ -378,7 +352,7 @@ QDateTime MessageEventModel::makeMessageTimestamp(const Quotient::Room::rev_iter
|
||||
};
|
||||
|
||||
// What kind of room is that?..
|
||||
qCCritical(MessageEvent) << "No valid timestamps in the room timeline!";
|
||||
qCritical() << "No valid timestamps in the room timeline!";
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -448,24 +422,12 @@ void MessageEventModel::fetchMore(const QModelIndex &parent)
|
||||
}
|
||||
}
|
||||
|
||||
static const QVariantMap emptyUser = {
|
||||
{"isLocalUser", false},
|
||||
{"id", QString()},
|
||||
{"avatarSource", QUrl()},
|
||||
{"avatarMediaId", QString()},
|
||||
{"avatarUrl", QString()},
|
||||
{"displayName", QString()},
|
||||
{"display", QString()},
|
||||
{"color", QColor()},
|
||||
{"object", QVariant()},
|
||||
};
|
||||
|
||||
inline QVariantMap userInContext(NeoChatUser *user, NeoChatRoom *room)
|
||||
inline QVariantMap userAtEvent(NeoChatUser *user, NeoChatRoom *room, const RoomEvent &evt)
|
||||
{
|
||||
Q_UNUSED(evt)
|
||||
return QVariantMap{
|
||||
{"isLocalUser", user->id() == room->localUser()->id()},
|
||||
{"id", user->id()},
|
||||
{"avatarSource", room->avatarForMember(user)},
|
||||
{"avatarMediaId", user->avatarMediaId(room)},
|
||||
{"avatarUrl", user->avatarUrl(room)},
|
||||
{"displayName", user->displayname(room)},
|
||||
@@ -475,8 +437,6 @@ inline QVariantMap userInContext(NeoChatUser *user, NeoChatRoom *room)
|
||||
};
|
||||
}
|
||||
|
||||
static LinkPreviewer *emptyLinkPreview = new LinkPreviewer;
|
||||
|
||||
QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
{
|
||||
const auto row = idx.row();
|
||||
@@ -533,7 +493,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return {};
|
||||
}
|
||||
|
||||
if (role == PlainText) {
|
||||
if (role == MessageRole) {
|
||||
return m_currentRoom->eventToString(evt);
|
||||
}
|
||||
|
||||
@@ -592,7 +552,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (role == AuthorRole) {
|
||||
auto author = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
|
||||
return userInContext(author, m_currentRoom);
|
||||
return userAtEvent(author, m_currentRoom, evt);
|
||||
}
|
||||
|
||||
if (role == ContentTypeRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
const auto &contentType = e->mimeType().name();
|
||||
return contentType == "text/plain" ? QStringLiteral("text/html") : contentType;
|
||||
}
|
||||
return QStringLiteral("text/plain");
|
||||
}
|
||||
|
||||
if (role == ContentRole) {
|
||||
@@ -621,6 +589,15 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return !m_currentRoom->isDirectChat() && m_currentRoom->isEventHighlighted(&evt);
|
||||
}
|
||||
|
||||
if (role == FileMimetypeIcon) {
|
||||
auto e = eventCast<const RoomMessageEvent>(&evt);
|
||||
if (!e || !e->hasFileContent()) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
return e->content()->fileInfo()->mimeType.iconName();
|
||||
}
|
||||
|
||||
if (role == MimeTypeRole) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
if (!e || !e->hasFileContent()) {
|
||||
@@ -706,22 +683,6 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return role == TimeRole ? QVariant(ts) : renderDate(ts);
|
||||
}
|
||||
|
||||
if (role == ShowLinkPreviewRole) {
|
||||
return m_linkPreviewers.contains(evt.id());
|
||||
}
|
||||
|
||||
if (role == LinkPreviewRole) {
|
||||
if (m_linkPreviewers.contains(evt.id())) {
|
||||
return QVariant::fromValue<LinkPreviewer *>(m_linkPreviewers[evt.id()]);
|
||||
} else {
|
||||
return QVariant::fromValue<LinkPreviewer *>(emptyLinkPreview);
|
||||
}
|
||||
}
|
||||
|
||||
if (role == MediaInfoRole) {
|
||||
return getMediaInfoForEvent(evt);
|
||||
}
|
||||
|
||||
if (role == IsReplyRole) {
|
||||
return !evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
||||
}
|
||||
@@ -730,27 +691,21 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
}
|
||||
|
||||
if (role == ReplyAuthor) {
|
||||
auto replyPtr = getReplyForEvent(evt);
|
||||
|
||||
if (replyPtr) {
|
||||
auto replyUser = static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId()));
|
||||
return userInContext(replyUser, m_currentRoom);
|
||||
} else {
|
||||
return emptyUser;
|
||||
}
|
||||
}
|
||||
|
||||
if (role == ReplyMediaInfoRole) {
|
||||
auto replyPtr = getReplyForEvent(evt);
|
||||
if (!replyPtr) {
|
||||
return {};
|
||||
}
|
||||
return getMediaInfoForEvent(*replyPtr);
|
||||
}
|
||||
|
||||
if (role == ReplyRole) {
|
||||
auto replyPtr = getReplyForEvent(evt);
|
||||
const QString &replyEventId = evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
if (replyEventId.isEmpty()) {
|
||||
return {};
|
||||
};
|
||||
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
||||
const RoomEvent *replyPtr = replyIt != m_currentRoom->historyEdge() ? &**replyIt : nullptr;
|
||||
if (!replyPtr) {
|
||||
for (const auto &e : m_extraEvents) {
|
||||
if (e->id() == replyEventId) {
|
||||
replyPtr = e.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!replyPtr) {
|
||||
return {};
|
||||
}
|
||||
@@ -787,10 +742,23 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
type = DelegateType::Other;
|
||||
}
|
||||
|
||||
return QVariantMap{
|
||||
{"display", m_currentRoom->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"type", type},
|
||||
QVariant content;
|
||||
if (auto e = eventCast<const RoomMessageEvent>(replyPtr)) {
|
||||
// Cannot use e.contentJson() here because some
|
||||
// EventContent classes inject values into the copy of the
|
||||
// content JSON stored in EventContent::Base
|
||||
content = e->hasFileContent() ? QVariant::fromValue(e->content()->originalJson) : QVariant();
|
||||
};
|
||||
|
||||
if (auto e = eventCast<const StickerEvent>(replyPtr)) {
|
||||
content = QVariant::fromValue(e->image().originalJson);
|
||||
}
|
||||
|
||||
return QVariantMap{{"eventId", replyEventId},
|
||||
{"display", m_currentRoom->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"content", content},
|
||||
{"type", type},
|
||||
{"author", userAtEvent(static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId())), m_currentRoom, evt)}};
|
||||
}
|
||||
|
||||
if (role == ShowAuthorRole) {
|
||||
@@ -853,13 +821,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
|
||||
if (role == ReadMarkersRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
auto userIds_temp = room()->userIdsAtEvent(evt.id());
|
||||
userIds_temp.remove(m_currentRoom->localUser()->id());
|
||||
|
||||
auto userIds = userIds_temp.values();
|
||||
if (userIds.count() > 5) {
|
||||
userIds = userIds.mid(0, 5);
|
||||
}
|
||||
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||
userIds.remove(m_currentRoom->localUser()->id());
|
||||
#else
|
||||
auto userIds = room()->usersAtEventId(evt.id());
|
||||
userIds.removeAll(m_currentRoom->localUser());
|
||||
@@ -873,28 +836,12 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
#else
|
||||
auto user = static_cast<NeoChatUser *>(userId);
|
||||
#endif
|
||||
users += userInContext(user, m_currentRoom);
|
||||
users += userAtEvent(user, m_currentRoom, evt);
|
||||
}
|
||||
|
||||
return users;
|
||||
}
|
||||
|
||||
if (role == ExcessReadMarkersRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||
userIds.remove(m_currentRoom->localUser()->id());
|
||||
#else
|
||||
auto userIds = room()->usersAtEventId(evt.id());
|
||||
userIds.removeAll(m_currentRoom->localUser());
|
||||
#endif
|
||||
|
||||
if (userIds.count() > 5) {
|
||||
return QStringLiteral("+ ") + QString::number(userIds.count() - 5);
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
}
|
||||
|
||||
if (role == ReadMarkersStringRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
auto userIds = room()->userIdsAtEvent(evt.id());
|
||||
@@ -955,7 +902,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
while (i != reactions.constEnd()) {
|
||||
QVariantList authors;
|
||||
for (auto author : i.value()) {
|
||||
authors.append(userInContext(author, m_currentRoom));
|
||||
authors.append(userAtEvent(author, m_currentRoom, evt));
|
||||
}
|
||||
bool hasLocalUser = i.value().contains(static_cast<NeoChatUser *>(m_currentRoom->localUser()));
|
||||
res.append(QVariantMap{{"reaction", i.key()}, {"count", i.value().count()}, {"authors", authors}, {"hasLocalUser", hasLocalUser}});
|
||||
@@ -968,6 +915,38 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
return evt.senderId();
|
||||
}
|
||||
|
||||
if (role == MediaUrlRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
if (auto e = eventCast<const RoomMessageEvent>(&evt)) {
|
||||
if (!e->hasFileContent()) {
|
||||
return QVariant();
|
||||
}
|
||||
if (e->content()->originalJson.contains(QStringLiteral("file")) && e->content()->originalJson["file"].toObject().contains(QStringLiteral("url"))) {
|
||||
return m_currentRoom->makeMediaUrl(e->id(), e->content()->originalJson["file"]["url"].toString());
|
||||
}
|
||||
if (e->content()->originalJson.contains(QStringLiteral("url"))) {
|
||||
return m_currentRoom->makeMediaUrl(e->id(), e->content()->originalJson["url"].toString());
|
||||
}
|
||||
}
|
||||
|
||||
// Requires https://github.com/quotient-im/libQuotient/pull/570
|
||||
// if (auto e = eventCast<const StickerEvent>(&evt)) {
|
||||
// return m_currentRoom->makeMediaUrl(e->id(), e->url());
|
||||
// }
|
||||
#endif
|
||||
|
||||
// Construct link in the same form as urlToDownload as that function doesn't work for stickers
|
||||
if (auto e = eventCast<const StickerEvent>(&evt)) {
|
||||
auto url = QUrl(m_currentRoom->connection()->homeserver().toString() + "/_matrix/media/r0/download/" + e->url().toString().remove("mxc://"));
|
||||
QUrlQuery q(url.query());
|
||||
q.addQueryItem("allow_remote", "true");
|
||||
url.setQuery(q);
|
||||
return url;
|
||||
}
|
||||
|
||||
return m_currentRoom->urlToDownload(evt.id());
|
||||
}
|
||||
|
||||
if (role == VerifiedRole) {
|
||||
#ifdef QUOTIENT_07
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
@@ -1107,174 +1086,6 @@ void MessageEventModel::loadReply(const QModelIndex &index)
|
||||
QPersistentModelIndex persistentIndex(index);
|
||||
connect(job, &BaseJob::success, this, [this, job, persistentIndex] {
|
||||
m_extraEvents.push_back(fromJson<event_ptr_tt<RoomEvent>>(job->jsonData()));
|
||||
Q_EMIT dataChanged(persistentIndex, persistentIndex, {ReplyRole, ReplyMediaInfoRole, ReplyAuthor});
|
||||
Q_EMIT dataChanged(persistentIndex, persistentIndex, {ReplyRole});
|
||||
});
|
||||
}
|
||||
|
||||
const RoomEvent *MessageEventModel::getReplyForEvent(const RoomEvent &event) const
|
||||
{
|
||||
const QString &replyEventId = event.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
if (replyEventId.isEmpty()) {
|
||||
return {};
|
||||
};
|
||||
|
||||
const auto replyIt = m_currentRoom->findInTimeline(replyEventId);
|
||||
const RoomEvent *replyPtr = replyIt != m_currentRoom->historyEdge() ? &**replyIt : nullptr;
|
||||
if (!replyPtr) {
|
||||
for (const auto &e : m_extraEvents) {
|
||||
if (e->id() == replyEventId) {
|
||||
replyPtr = e.get();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return replyPtr;
|
||||
}
|
||||
|
||||
QVariantMap MessageEventModel::getMediaInfoForEvent(const RoomEvent &event) const
|
||||
{
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
QString eventId = event.id();
|
||||
|
||||
// Get the file info for the event.
|
||||
const EventContent::FileInfo *fileInfo;
|
||||
#ifdef QUOTIENT_07
|
||||
if (event.is<RoomMessageEvent>()) {
|
||||
auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event);
|
||||
#else
|
||||
if (auto roomMessageEvent = eventCast<const RoomMessageEvent>(&event)) {
|
||||
#endif
|
||||
if (!roomMessageEvent->hasFileContent()) {
|
||||
return {};
|
||||
}
|
||||
fileInfo = roomMessageEvent->content()->fileInfo();
|
||||
#ifdef QUOTIENT_07
|
||||
} else if (event.is<StickerEvent>()) {
|
||||
auto stickerEvent = eventCast<const StickerEvent>(&event);
|
||||
#else
|
||||
} else if (auto stickerEvent = eventCast<const StickerEvent>(&event)) {
|
||||
#endif
|
||||
fileInfo = &stickerEvent->image();
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
|
||||
return getMediaInfoFromFileInfo(fileInfo, eventId);
|
||||
}
|
||||
|
||||
QVariantMap MessageEventModel::getMediaInfoFromFileInfo(const EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail) const
|
||||
{
|
||||
QVariantMap mediaInfo;
|
||||
|
||||
// Get the mxc URL for the media.
|
||||
#ifdef QUOTIENT_07
|
||||
if (!fileInfo->url().isValid() || eventId.isEmpty()) {
|
||||
mediaInfo["source"] = QUrl();
|
||||
} else {
|
||||
QUrl source = m_currentRoom->makeMediaUrl(eventId, fileInfo->url());
|
||||
|
||||
if (source.isValid() && source.scheme() == QStringLiteral("mxc")) {
|
||||
mediaInfo["source"] = source;
|
||||
} else {
|
||||
mediaInfo["source"] = QUrl();
|
||||
}
|
||||
}
|
||||
#else
|
||||
auto url = QUrl(m_currentRoom->connection()->homeserver().toString() + "/_matrix/media/r0/download/" + fileInfo->url.toString().remove("mxc://"));
|
||||
QUrlQuery q(url.query());
|
||||
q.addQueryItem("allow_remote", "true");
|
||||
url.setQuery(q);
|
||||
mediaInfo["source"] = url;
|
||||
#endif
|
||||
|
||||
auto mimeType = fileInfo->mimeType;
|
||||
// Add the MIME type for the media if available.
|
||||
mediaInfo["mimeType"] = mimeType.name();
|
||||
|
||||
// Add the MIME type icon if available.
|
||||
mediaInfo["mimeIcon"] = mimeType.iconName();
|
||||
|
||||
// Add media size if available.
|
||||
mediaInfo["size"] = fileInfo->payloadSize;
|
||||
|
||||
// Add parameter depending on media type.
|
||||
if (mimeType.name().contains(QStringLiteral("image"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileInfo)) {
|
||||
mediaInfo["width"] = castInfo->imageSize.width();
|
||||
mediaInfo["height"] = castInfo->imageSize.height();
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"].toUrl().scheme() == "mxc") {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"].toString();
|
||||
if (blurhash.isEmpty()) {
|
||||
tempInfo["source"] = QUrl();
|
||||
} else {
|
||||
tempInfo["source"] = QUrl("image://blurhash/" + blurhash);
|
||||
}
|
||||
}
|
||||
mediaInfo["tempInfo"] = tempInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("video"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::VideoContent *>(fileInfo)) {
|
||||
mediaInfo["width"] = castInfo->imageSize.width();
|
||||
mediaInfo["height"] = castInfo->imageSize.height();
|
||||
mediaInfo["duration"] = castInfo->duration;
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"].toUrl().scheme() == "mxc") {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"].toString();
|
||||
if (blurhash.isEmpty()) {
|
||||
tempInfo["source"] = QUrl();
|
||||
} else {
|
||||
tempInfo["source"] = QUrl("image://blurhash/" + blurhash);
|
||||
}
|
||||
}
|
||||
mediaInfo["tempInfo"] = tempInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("audio"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::AudioContent *>(fileInfo)) {
|
||||
mediaInfo["duration"] = castInfo->duration;
|
||||
}
|
||||
}
|
||||
|
||||
return mediaInfo;
|
||||
}
|
||||
|
||||
void MessageEventModel::createLinkPreviewerForEvent(const Quotient::RoomMessageEvent *event)
|
||||
{
|
||||
if (m_linkPreviewers.contains(event->id())) {
|
||||
return;
|
||||
} else {
|
||||
QString text;
|
||||
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) {
|
||||
m_linkPreviewers[event->id()] = new LinkPreviewer(nullptr, m_currentRoom, links.size() > 0 ? links[0] : QUrl());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
#include "linkpreviewer.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
/**
|
||||
@@ -59,39 +58,35 @@ public:
|
||||
*/
|
||||
enum EventRoles {
|
||||
DelegateTypeRole = Qt::UserRole + 1, /**< The delegate type of the message. */
|
||||
PlainText, /**< Plain text representation of the message. */
|
||||
MessageRole, /**< Plain text representation of the message. */
|
||||
EventIdRole, /**< The matrix event ID of the event. */
|
||||
TimeRole, /**< The timestamp for when the event was sent. */
|
||||
SectionRole, /**< The date of the event as a string. */
|
||||
AuthorRole, /**< The author of the event. */
|
||||
ContentRole, /**< The full message content. */
|
||||
ContentTypeRole, /**< The content mime type. */
|
||||
HighlightRole, /**< Whether the event should be highlighted. */
|
||||
SpecialMarksRole, /**< Whether the event is hidden or not. */
|
||||
LongOperationRole, /**< Progress info when downloading files. */
|
||||
FormattedBodyRole, /**< The formatted body of a rich message. */
|
||||
GenericDisplayRole, /**< A generic string based upon the message type. */
|
||||
|
||||
ShowLinkPreviewRole, /**< Whether a link preview should be shown. */
|
||||
LinkPreviewRole, /**< The link preview details. */
|
||||
|
||||
MediaInfoRole, /**< The media info for the event. */
|
||||
MimeTypeRole, /**< The mime type of the message's file or media. */
|
||||
FileMimetypeIcon, /**< The icon name for the mime type of a file. */
|
||||
|
||||
IsReplyRole, /**< Is the message a reply to another event. */
|
||||
ReplyAuthor, /**< The author of the event that was replied to. */
|
||||
ReplyIdRole, /**< The matrix ID of the message that was replied to. */
|
||||
ReplyMediaInfoRole, /**< The media info of the message that was replied to. */
|
||||
ReplyRole, /**< The content data of the message that was replied to. */
|
||||
ReplyIdRole, /**< The matrix ID of the message that was replied to. */
|
||||
|
||||
ShowAuthorRole, /**< Whether the author's name should be shown. */
|
||||
ShowSectionRole, /**< Whether the section header should be shown. */
|
||||
|
||||
ReadMarkersRole, /**< The first 5 other users at the event for read marker tracking. */
|
||||
ExcessReadMarkersRole, /**< The number of other users at the event after the first 5. */
|
||||
ReadMarkersRole, /**< Other users at the event for read marker tracking. */
|
||||
ReadMarkersStringRole, /**< String with the display name and mxID of the users at the event. */
|
||||
ShowReadMarkersRole, /**< Whether there are any other user read markers to be shown. */
|
||||
ReactionRole, /**< List of reactions to this event. */
|
||||
SourceRole, /**< The full message source JSON. */
|
||||
MediaUrlRole, /**< The source URL for any media in the message. */
|
||||
|
||||
// For debugging
|
||||
EventResolvedTypeRole, /**< The event type the message. */
|
||||
@@ -185,8 +180,6 @@ private:
|
||||
int rowBelowInserted = -1;
|
||||
bool movingEvent = false;
|
||||
|
||||
QMap<QString, LinkPreviewer *> m_linkPreviewers;
|
||||
|
||||
[[nodiscard]] int timelineBaseIndex() const;
|
||||
[[nodiscard]] QDateTime makeMessageTimestamp(const Quotient::Room::rev_iter_t &baseIt) const;
|
||||
[[nodiscard]] static QString renderDate(const QDateTime ×tamp);
|
||||
@@ -199,14 +192,7 @@ private:
|
||||
int refreshEventRoles(const QString &eventId, const QVector<int> &roles = {});
|
||||
void moveReadMarker(const QString &toEventId);
|
||||
|
||||
const Quotient::RoomEvent *getReplyForEvent(const Quotient::RoomEvent &event) const;
|
||||
QVariantMap getMediaInfoForEvent(const Quotient::RoomEvent &event) const;
|
||||
QVariantMap getMediaInfoFromFileInfo(const Quotient::EventContent::FileInfo *fileInfo, const QString &eventId, bool isThumbnail = false) const;
|
||||
void createLinkPreviewerForEvent(const Quotient::RoomMessageEvent *event);
|
||||
|
||||
std::vector<Quotient::event_ptr_tt<Quotient::RoomEvent>> m_extraEvents;
|
||||
// Hack to ensure that we don't call endInsertRows when we haven't called beginInsertRows
|
||||
bool m_initialized = false;
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "roommanager.h"
|
||||
#include "spacehierarchycache.h"
|
||||
#include "user.h"
|
||||
|
||||
#include <QDebug>
|
||||
@@ -66,9 +65,6 @@ RoomListModel::RoomListModel(QObject *parent)
|
||||
qGuiApp->setBadgeNumber(m_notificationCount);
|
||||
#endif // QT_VERSION_CHECK(6, 6, 0)
|
||||
});
|
||||
connect(&SpaceHierarchyCache::instance(), &SpaceHierarchyCache::spaceHierarchyChanged, this, [this]() {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount(), 0), {IsChildSpaceRole});
|
||||
});
|
||||
}
|
||||
|
||||
RoomListModel::~RoomListModel() = default;
|
||||
@@ -161,7 +157,7 @@ void RoomListModel::connectRoomSignals(NeoChatRoom *room)
|
||||
refresh(room, {DisplayNameRole, NameRole});
|
||||
});
|
||||
connect(room, &Room::unreadMessagesChanged, this, [this, room] {
|
||||
refresh(room, {NotificationCountRole, HighlightCountRole});
|
||||
refresh(room, {UnreadCountRole, NotificationCountRole, HighlightCountRole});
|
||||
});
|
||||
connect(room, &Room::notificationCountChanged, this, [this, room] {
|
||||
refresh(room);
|
||||
@@ -374,6 +370,9 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
return NeoChatRoomType::Normal;
|
||||
}
|
||||
if (role == UnreadCountRole) {
|
||||
return room->unreadCount();
|
||||
}
|
||||
if (role == NotificationCountRole) {
|
||||
return room->notificationCount();
|
||||
}
|
||||
@@ -413,9 +412,6 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
if (role == IsSpaceRole) {
|
||||
return room->isSpace();
|
||||
}
|
||||
if (role == IsChildSpaceRole) {
|
||||
return SpaceHierarchyCache::instance().isChildSpace(room->id());
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
@@ -440,6 +436,7 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||
roles[CanonicalAliasRole] = "canonicalAlias";
|
||||
roles[TopicRole] = "topic";
|
||||
roles[CategoryRole] = "category";
|
||||
roles[UnreadCountRole] = "unreadCount";
|
||||
roles[NotificationCountRole] = "notificationCount";
|
||||
roles[HighlightCountRole] = "highlightCount";
|
||||
roles[LastEventRole] = "lastEvent";
|
||||
@@ -450,7 +447,6 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||
roles[SubtitleTextRole] = "subtitleText";
|
||||
roles[IsSpaceRole] = "isSpace";
|
||||
roles[IdRole] = "id";
|
||||
roles[IsChildSpaceRole] = "isChildSpace";
|
||||
return roles;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ public:
|
||||
CanonicalAliasRole, /**< The room canonical alias. */
|
||||
TopicRole, /**< The room topic. */
|
||||
CategoryRole, /**< The room category, e.g favourite. */
|
||||
UnreadCountRole, /**< The number of unread messages in the room. */
|
||||
NotificationCountRole, /**< The number of notifications in the room. */
|
||||
HighlightCountRole, /**< The number of highlighted messages in the room. */
|
||||
LastEventRole, /**< Text for the last event in the room. */
|
||||
@@ -75,7 +76,6 @@ public:
|
||||
AvatarImageRole, /**< The room avatar as an image. */
|
||||
IdRole, /**< The room matrix ID. */
|
||||
IsSpaceRole, /**< Whether the room is a space. */
|
||||
IsChildSpaceRole, /**< Whether this space is a child of a different space. */
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
|
||||
|
||||
@@ -5,44 +5,12 @@
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class SortFilterRoomListModel
|
||||
*
|
||||
* This model sorts and filters the room list.
|
||||
*
|
||||
* There are numerous room sort orders available:
|
||||
* - Categories - sort rooms by their NeoChatRoomType and then by last activty within
|
||||
* each category.
|
||||
* - LastActivity - sort rooms by the last active time in the room.
|
||||
* - Alphabetical - sort the rooms alphabetically by room name.
|
||||
*
|
||||
* The model can be given a filter string that will only show rooms who's name includes
|
||||
* the text.
|
||||
*
|
||||
* The model can also be given an active space ID and will only show rooms within
|
||||
* that space.
|
||||
*
|
||||
* All space rooms and upgraded rooms will also be filtered out.
|
||||
*/
|
||||
class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The order by which the rooms will be sorted.
|
||||
*
|
||||
* @sa RoomSortOrder
|
||||
*/
|
||||
Q_PROPERTY(RoomSortOrder roomSortOrder READ roomSortOrder WRITE setRoomSortOrder NOTIFY roomSortOrderChanged)
|
||||
|
||||
/**
|
||||
* @brief The text to use to filter room names.
|
||||
*/
|
||||
Q_PROPERTY(QString filterText READ filterText READ filterText WRITE setFilterText NOTIFY filterTextChanged)
|
||||
|
||||
/**
|
||||
* @brief Set the ID of the space to show rooms for.
|
||||
*/
|
||||
Q_PROPERTY(QString activeSpaceId READ activeSpaceId WRITE setActiveSpaceId NOTIFY activeSpaceIdChanged)
|
||||
|
||||
public:
|
||||
@@ -61,22 +29,12 @@ public:
|
||||
void setFilterText(const QString &text);
|
||||
[[nodiscard]] QString filterText() const;
|
||||
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
QString activeSpaceId() const;
|
||||
void setActiveSpaceId(const QString &spaceId);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Returns true if the value of source_left is less than source_right.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::lessThan
|
||||
*/
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
/**
|
||||
* @brief Whether a row should be shown out or not.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||
*/
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
@@ -11,24 +11,13 @@ SortFilterSpaceListModel::SortFilterSpaceListModel(QObject *parent)
|
||||
setSortRole(RoomListModel::IdRole);
|
||||
sort(0);
|
||||
invalidateFilter();
|
||||
connect(this, &QAbstractProxyModel::sourceModelChanged, this, [this]() {
|
||||
connect(sourceModel(), &QAbstractListModel::dataChanged, this, [this](const QModelIndex &, const QModelIndex &, QVector<int> roles) {
|
||||
if (roles.contains(RoomListModel::IsChildSpaceRole)) {
|
||||
invalidate();
|
||||
}
|
||||
countChanged();
|
||||
});
|
||||
invalidate();
|
||||
Q_EMIT countChanged();
|
||||
});
|
||||
}
|
||||
|
||||
bool SortFilterSpaceListModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool()
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded"
|
||||
&& !sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsChildSpaceRole).toBool();
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded";
|
||||
}
|
||||
|
||||
bool SortFilterSpaceListModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||
|
||||
@@ -5,40 +5,20 @@
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class SortFilterSpaceListModel
|
||||
*
|
||||
* This model sorts and filters the space list.
|
||||
*
|
||||
* The spaces are sorted by their matrix ID. The filter only shows space rooms,
|
||||
* but filters out upgraded spaces.
|
||||
*/
|
||||
class SortFilterSpaceListModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
/**
|
||||
* @brief The number of spaces in the model.
|
||||
*/
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
|
||||
public:
|
||||
explicit SortFilterSpaceListModel(QObject *parent = nullptr);
|
||||
|
||||
Q_SIGNALS:
|
||||
void countChanged();
|
||||
[[nodiscard]] QString activeSpaceId() const;
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Returns true if the value of source_left is less than source_right.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::lessThan
|
||||
*/
|
||||
[[nodiscard]] bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||
|
||||
/**
|
||||
* @brief Whether a row should be shown out or not.
|
||||
*
|
||||
* @sa QSortFilterProxyModel::filterAcceptsRow
|
||||
*/
|
||||
Q_SIGNALS:
|
||||
void activeSpaceIdChanged(QString &activeSpaceId);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
|
||||
};
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#include "statefiltermodel.h"
|
||||
|
||||
#include "statemodel.h"
|
||||
|
||||
bool StateFilterModel::filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const
|
||||
{
|
||||
Q_UNUSED(sourceParent);
|
||||
// No need to run the check if there are no items in m_stateEventTypesFiltered.
|
||||
if (m_stateEventTypesFiltered.empty()) {
|
||||
return true;
|
||||
}
|
||||
return !m_stateEventTypesFiltered.contains(sourceModel()->data(sourceModel()->index(sourceRow, 0), StateModel::TypeRole).toString());
|
||||
}
|
||||
|
||||
void StateFilterModel::addStateEventTypeFiltered(const QString &stateEventType)
|
||||
{
|
||||
if (!m_stateEventTypesFiltered.contains(stateEventType)) {
|
||||
m_stateEventTypesFiltered.append(stateEventType);
|
||||
invalidateFilter();
|
||||
}
|
||||
}
|
||||
|
||||
void StateFilterModel::removeStateEventTypeFiltered(const QString &stateEventType)
|
||||
{
|
||||
if (m_stateEventTypesFiltered.contains(stateEventType)) {
|
||||
m_stateEventTypesFiltered.removeAll(stateEventType);
|
||||
invalidateFilter();
|
||||
}
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2022 James Graham <james.h.graham@protonmail.com>
|
||||
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QSortFilterProxyModel>
|
||||
|
||||
/**
|
||||
* @class StateFilterModel
|
||||
*
|
||||
* This class creates a custom QSortFilterProxyModel for filtering a list of state events.
|
||||
* Event types can be filtered out by adding them to m_stateEventTypesFiltered.
|
||||
*/
|
||||
class StateFilterModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Custom filter function checking if an event type has been filtered out.
|
||||
*
|
||||
* The filter rejects a row if the state event type has been added to m_stateEventTypesFiltered.
|
||||
*
|
||||
* @sa m_stateEventTypesFiltered
|
||||
*/
|
||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||
|
||||
/**
|
||||
* @brief Add an event type to m_stateEventTypesFiltered.
|
||||
*
|
||||
* @sa m_stateEventTypesFiltered
|
||||
*/
|
||||
Q_INVOKABLE void addStateEventTypeFiltered(const QString &stateEventType);
|
||||
|
||||
/**
|
||||
* @brief Remove an event type from m_stateEventTypesFiltered.
|
||||
*
|
||||
* @sa m_stateEventTypesFiltered
|
||||
*/
|
||||
Q_INVOKABLE void removeStateEventTypeFiltered(const QString &stateEventType);
|
||||
|
||||
private:
|
||||
QStringList m_stateEventTypesFiltered;
|
||||
};
|
||||
@@ -10,7 +10,7 @@ StateModel::StateModel(QObject *parent)
|
||||
|
||||
QHash<int, QByteArray> StateModel::roleNames() const
|
||||
{
|
||||
return {{TypeRole, "type"}, {StateKeyRole, "stateKey"}};
|
||||
return {{TypeRole, "type"}, {StateKeyRole, "stateKey"}, {SourceRole, "source"}};
|
||||
}
|
||||
QVariant StateModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
@@ -18,9 +18,11 @@ QVariant StateModel::data(const QModelIndex &index, int role) const
|
||||
auto row = index.row();
|
||||
switch (role) {
|
||||
case TypeRole:
|
||||
return m_stateEvents[row].first;
|
||||
return m_room->currentState().events().keys()[row].first;
|
||||
case StateKeyRole:
|
||||
return m_stateEvents[row].second;
|
||||
return m_room->currentState().events().keys()[row].second;
|
||||
case SourceRole:
|
||||
return QJsonDocument(m_room->currentState().events()[m_room->currentState().events().keys()[row]]->fullJson()).toJson();
|
||||
}
|
||||
#endif
|
||||
return {};
|
||||
@@ -46,27 +48,9 @@ void StateModel::setRoom(NeoChatRoom *room)
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
beginResetModel();
|
||||
m_stateEvents.clear();
|
||||
#ifdef QUOTIENT_07
|
||||
m_stateEvents = m_room->currentState().events().keys();
|
||||
#endif
|
||||
endResetModel();
|
||||
connect(room, &NeoChatRoom::changed, this, [this] {
|
||||
beginResetModel();
|
||||
m_stateEvents.clear();
|
||||
#ifdef QUOTIENT_07
|
||||
m_stateEvents = m_room->currentState().events().keys();
|
||||
#endif
|
||||
endResetModel();
|
||||
});
|
||||
}
|
||||
|
||||
QByteArray StateModel::stateEventJson(const QModelIndex &index)
|
||||
{
|
||||
auto row = index.row();
|
||||
#ifdef QUOTIENT_07
|
||||
return QJsonDocument(m_room->currentState().events()[m_stateEvents[row]]->fullJson()).toJson();
|
||||
#else
|
||||
return {};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -7,74 +7,31 @@
|
||||
|
||||
#include "neochatroom.h"
|
||||
|
||||
/**
|
||||
* @class StateModel
|
||||
*
|
||||
* This class defines the model for visualising the state events in a room.
|
||||
*/
|
||||
class StateModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is getting its state events from.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
TypeRole = 0, /**< The type of the state event. */
|
||||
StateKeyRole, /**< The state key of the state event. */
|
||||
TypeRole,
|
||||
StateKeyRole,
|
||||
SourceRole,
|
||||
};
|
||||
Q_ENUM(Roles);
|
||||
|
||||
StateModel(QObject *parent = nullptr);
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
||||
NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
/**
|
||||
* @brief Get the full JSON for an event.
|
||||
*
|
||||
* This is used to avoid having the model hold all the JSON data. The JSON for
|
||||
* a single item is only ever shown, no need to access simultaneously.
|
||||
*/
|
||||
Q_INVOKABLE QByteArray stateEventJson(const QModelIndex &index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
|
||||
private:
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
|
||||
/**
|
||||
* @brief The room state events in a QList.
|
||||
*
|
||||
* This is done for performance, accessing all the data from the parent QHash
|
||||
* was slower.
|
||||
*/
|
||||
QList<std::pair<QString, QString>> m_stateEvents;
|
||||
};
|
||||
|
||||
@@ -1,109 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021-2023 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "stickermodel.h"
|
||||
|
||||
#include "models/imagepacksmodel.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
StickerModel::StickerModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
int StickerModel::rowCount(const QModelIndex &index) const
|
||||
{
|
||||
return m_images.size();
|
||||
}
|
||||
QVariant StickerModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
const auto &row = index.row();
|
||||
const auto &image = m_images[row];
|
||||
if (role == Url) {
|
||||
#ifdef QUOTIENT_07
|
||||
return m_room->connection()->makeMediaUrl(image.url);
|
||||
#endif
|
||||
}
|
||||
if (role == Body) {
|
||||
if (image.body) {
|
||||
return *image.body;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> StickerModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{StickerModel::Url, "url"},
|
||||
{StickerModel::Body, "body"},
|
||||
};
|
||||
}
|
||||
ImagePacksModel *StickerModel::model() const
|
||||
{
|
||||
return m_model;
|
||||
}
|
||||
|
||||
void StickerModel::setModel(ImagePacksModel *model)
|
||||
{
|
||||
if (m_model) {
|
||||
disconnect(m_model, nullptr, this, nullptr);
|
||||
}
|
||||
connect(model, &ImagePacksModel::roomChanged, this, [this]() {
|
||||
beginResetModel();
|
||||
m_images = m_model->images(m_index);
|
||||
endResetModel();
|
||||
});
|
||||
beginResetModel();
|
||||
m_model = model;
|
||||
m_images = m_model->images(m_index);
|
||||
endResetModel();
|
||||
Q_EMIT modelChanged();
|
||||
}
|
||||
|
||||
int StickerModel::packIndex() const
|
||||
{
|
||||
return m_index;
|
||||
}
|
||||
void StickerModel::setPackIndex(int index)
|
||||
{
|
||||
beginResetModel();
|
||||
m_index = index;
|
||||
if (m_model) {
|
||||
m_images = m_model->images(m_index);
|
||||
}
|
||||
endResetModel();
|
||||
Q_EMIT packIndexChanged();
|
||||
}
|
||||
|
||||
NeoChatRoom *StickerModel::room() const
|
||||
{
|
||||
return m_room;
|
||||
}
|
||||
|
||||
void StickerModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
void StickerModel::postSticker(int index)
|
||||
{
|
||||
const auto &image = m_images[index];
|
||||
const auto &body = image.body ? *image.body : QString();
|
||||
QJsonObject infoJson;
|
||||
if (image.info) {
|
||||
infoJson["w"] = image.info->imageSize.width();
|
||||
infoJson["h"] = image.info->imageSize.height();
|
||||
infoJson["mimetype"] = image.info->mimeType.name();
|
||||
infoJson["size"] = image.info->payloadSize;
|
||||
// TODO thumbnail
|
||||
}
|
||||
QJsonObject content{
|
||||
{"body"_ls, body},
|
||||
{"url"_ls, image.url.toString()},
|
||||
{"info"_ls, infoJson},
|
||||
};
|
||||
m_room->postJson("m.sticker", content);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "events/imagepackevent.h"
|
||||
#include "neochatroom.h"
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QVector>
|
||||
|
||||
class ImagePacksModel;
|
||||
|
||||
/**
|
||||
* @class StickerModel
|
||||
*
|
||||
* A model to visualise a set of stickers.
|
||||
*
|
||||
* The stickers are obtained from a Matrix image pack. See Matrix MSC2545 for more details.
|
||||
* https://github.com/Sorunome/matrix-doc/blob/soru/emotes/proposals/2545-emotes.md
|
||||
*/
|
||||
class StickerModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The image pack that the stickers come from.
|
||||
*
|
||||
* @sa ImagePacksModel
|
||||
*/
|
||||
Q_PROPERTY(ImagePacksModel *model READ model WRITE setModel NOTIFY modelChanged)
|
||||
|
||||
/**
|
||||
* @brief The index of the pack in the ImagePacksModel.
|
||||
*
|
||||
* @sa ImagePacksModel
|
||||
*/
|
||||
Q_PROPERTY(int packIndex READ packIndex WRITE setPackIndex NOTIFY packIndexChanged)
|
||||
|
||||
/**
|
||||
* @brief The current room that the model is being used in.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Roles {
|
||||
Url = Qt::UserRole + 1, /**< The source mxc URL for the image. */
|
||||
Body, /**< The image caption, if any. */
|
||||
};
|
||||
|
||||
explicit StickerModel(QObject *parent = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &index) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa Roles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
[[nodiscard]] ImagePacksModel *model() const;
|
||||
void setModel(ImagePacksModel *model);
|
||||
|
||||
[[nodiscard]] int packIndex() const;
|
||||
void setPackIndex(int index);
|
||||
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief Post the sticker at the given index as an event in the room.
|
||||
*/
|
||||
Q_INVOKABLE void postSticker(int index);
|
||||
|
||||
Q_SIGNALS:
|
||||
void roomChanged();
|
||||
void modelChanged();
|
||||
void packIndexChanged();
|
||||
|
||||
private:
|
||||
ImagePacksModel *m_model = nullptr;
|
||||
int m_index = 0;
|
||||
QVector<Quotient::ImagePackEventContent::ImagePackImage> m_images;
|
||||
NeoChatRoom *m_room;
|
||||
};
|
||||
@@ -13,11 +13,6 @@ UserDirectoryListModel::UserDirectoryListModel(QObject *parent)
|
||||
{
|
||||
}
|
||||
|
||||
Quotient::Connection *UserDirectoryListModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void UserDirectoryListModel::setConnection(Connection *conn)
|
||||
{
|
||||
if (m_connection == conn) {
|
||||
@@ -47,11 +42,6 @@ void UserDirectoryListModel::setConnection(Connection *conn)
|
||||
Q_EMIT limitedChanged();
|
||||
}
|
||||
|
||||
QString UserDirectoryListModel::keyword() const
|
||||
{
|
||||
return m_keyword;
|
||||
}
|
||||
|
||||
void UserDirectoryListModel::setKeyword(const QString &value)
|
||||
{
|
||||
if (m_keyword == value) {
|
||||
@@ -72,11 +62,6 @@ void UserDirectoryListModel::setKeyword(const QString &value)
|
||||
Q_EMIT limitedChanged();
|
||||
}
|
||||
|
||||
bool UserDirectoryListModel::limited() const
|
||||
{
|
||||
return m_limited;
|
||||
}
|
||||
|
||||
void UserDirectoryListModel::search(int count)
|
||||
{
|
||||
if (count < 1) {
|
||||
|
||||
@@ -13,86 +13,46 @@ namespace Quotient
|
||||
class Connection;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class UserDirectoryListModel
|
||||
*
|
||||
* This class defines the model for visualising the results of a user search.
|
||||
*
|
||||
* The model searches for users that have the given keyword in the matrix
|
||||
* ID or the display name. See
|
||||
* https://spec.matrix.org/v1.6/client-server-api/#post_matrixclientv3user_directorysearch
|
||||
* for more info on matrix user searches.
|
||||
*/
|
||||
class UserDirectoryListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The current connection that the model is getting users from.
|
||||
*/
|
||||
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The keyword to use in the search.
|
||||
*/
|
||||
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the current results have been truncated.
|
||||
*/
|
||||
Q_PROPERTY(bool limited READ limited NOTIFY limitedChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
NameRole = Qt::DisplayRole + 1, /**< 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. */
|
||||
NameRole = Qt::DisplayRole + 1,
|
||||
AvatarRole,
|
||||
UserIDRole,
|
||||
DirectChatsRole,
|
||||
};
|
||||
|
||||
UserDirectoryListModel(QObject *parent = nullptr);
|
||||
|
||||
[[nodiscard]] Quotient::Connection *connection() const;
|
||||
void setConnection(Quotient::Connection *conn);
|
||||
|
||||
[[nodiscard]] QString keyword() const;
|
||||
void setKeyword(const QString &value);
|
||||
|
||||
[[nodiscard]] bool limited() 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;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Start the user search.
|
||||
*/
|
||||
Q_INVOKABLE void search(int count = 50);
|
||||
[[nodiscard]] Quotient::Connection *connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
void setConnection(Quotient::Connection *conn);
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void keywordChanged();
|
||||
void limitedChanged();
|
||||
[[nodiscard]] QString keyword() const
|
||||
{
|
||||
return m_keyword;
|
||||
}
|
||||
void setKeyword(const QString &value);
|
||||
|
||||
[[nodiscard]] bool limited() const
|
||||
{
|
||||
return m_limited;
|
||||
}
|
||||
|
||||
Q_INVOKABLE void search(int count = 50);
|
||||
|
||||
private:
|
||||
Quotient::Connection *m_connection = nullptr;
|
||||
@@ -104,4 +64,9 @@ private:
|
||||
QVector<Quotient::SearchUserDirectoryJob::User> users;
|
||||
|
||||
Quotient::SearchUserDirectoryJob *job = nullptr;
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void keywordChanged();
|
||||
void limitedChanged();
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <events/roompowerlevelsevent.h>
|
||||
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -22,20 +23,40 @@ void UserListModel::setRoom(NeoChatRoom *room)
|
||||
return;
|
||||
}
|
||||
|
||||
beginResetModel();
|
||||
if (m_currentRoom) {
|
||||
m_currentRoom->disconnect(this);
|
||||
// m_currentRoom->connection()->disconnect(this);
|
||||
for (User *user : std::as_const(m_users)) {
|
||||
user->disconnect(this);
|
||||
}
|
||||
m_users.clear();
|
||||
}
|
||||
m_currentRoom = room;
|
||||
|
||||
if (m_currentRoom) {
|
||||
connect(m_currentRoom, &Room::userAdded, this, &UserListModel::userAdded);
|
||||
connect(m_currentRoom, &Room::userRemoved, this, &UserListModel::userRemoved);
|
||||
connect(m_currentRoom, &Room::memberAboutToRename, this, &UserListModel::userRemoved);
|
||||
connect(m_currentRoom, &Room::memberRenamed, this, &UserListModel::userAdded);
|
||||
connect(m_currentRoom, &Room::changed, this, &UserListModel::refreshAllUsers);
|
||||
connect(m_currentRoom, &Room::changed, this, &UserListModel::refreshAll);
|
||||
{
|
||||
m_users = m_currentRoom->users();
|
||||
std::sort(m_users.begin(), m_users.end(), room->memberSorter());
|
||||
}
|
||||
for (User *user : std::as_const(m_users)) {
|
||||
#ifdef QUOTIENT_07
|
||||
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
||||
avatarChanged(user, m_currentRoom);
|
||||
});
|
||||
#else
|
||||
connect(user, &User::avatarChanged, this, &UserListModel::avatarChanged);
|
||||
#endif
|
||||
}
|
||||
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
||||
setRoom(nullptr);
|
||||
});
|
||||
}
|
||||
|
||||
refreshAllUsers();
|
||||
endResetModel();
|
||||
Q_EMIT roomChanged();
|
||||
}
|
||||
|
||||
@@ -59,9 +80,7 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
|
||||
if (index.row() >= m_users.count()) {
|
||||
qDebug() << "UserListModel, something's wrong: index.row() >= "
|
||||
"users.count()";
|
||||
return {};
|
||||
return QStringLiteral("DEADBEEF");
|
||||
}
|
||||
auto user = m_users.at(index.row());
|
||||
if (role == NameRole) {
|
||||
@@ -71,8 +90,7 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
||||
return user->id();
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
auto neoChatUser = static_cast<NeoChatUser *>(user);
|
||||
return m_currentRoom->avatarForMember(neoChatUser);
|
||||
return user->avatarMediaId(m_currentRoom);
|
||||
}
|
||||
if (role == ObjectRole) {
|
||||
return QVariant::fromValue(user);
|
||||
@@ -115,6 +133,7 @@ int UserListModel::rowCount(const QModelIndex &parent) const
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return m_users.count();
|
||||
}
|
||||
|
||||
@@ -126,12 +145,10 @@ void UserListModel::userAdded(Quotient::User *user)
|
||||
endInsertRows();
|
||||
#ifdef QUOTIENT_07
|
||||
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
||||
refreshUser(user, {AvatarRole});
|
||||
avatarChanged(user, m_currentRoom);
|
||||
});
|
||||
#else
|
||||
connect(user, &Quotient::User::avatarChanged, this, [this, user]() {
|
||||
refreshUser(user, {AvatarRole});
|
||||
});
|
||||
connect(user, &Quotient::User::avatarChanged, this, &UserListModel::avatarChanged);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -148,7 +165,7 @@ void UserListModel::userRemoved(Quotient::User *user)
|
||||
}
|
||||
}
|
||||
|
||||
void UserListModel::refreshUser(Quotient::User *user, const QVector<int> &roles)
|
||||
void UserListModel::refresh(Quotient::User *user, const QVector<int> &roles)
|
||||
{
|
||||
auto pos = findUserPos(user);
|
||||
if (pos != m_users.size()) {
|
||||
@@ -158,7 +175,7 @@ void UserListModel::refreshUser(Quotient::User *user, const QVector<int> &roles)
|
||||
}
|
||||
}
|
||||
|
||||
void UserListModel::refreshAllUsers()
|
||||
void UserListModel::refreshAll()
|
||||
{
|
||||
beginResetModel();
|
||||
for (User *user : std::as_const(m_users)) {
|
||||
@@ -166,18 +183,17 @@ void UserListModel::refreshAllUsers()
|
||||
}
|
||||
m_users.clear();
|
||||
|
||||
m_users = m_currentRoom->users();
|
||||
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
|
||||
|
||||
{
|
||||
m_users = m_currentRoom->users();
|
||||
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
|
||||
}
|
||||
for (User *user : std::as_const(m_users)) {
|
||||
#ifdef QUOTIENT_07
|
||||
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
||||
refreshUser(user, {AvatarRole});
|
||||
avatarChanged(user, m_currentRoom);
|
||||
});
|
||||
#else
|
||||
connect(user, &User::avatarChanged, this, [this, user]() {
|
||||
refreshUser(user, {AvatarRole});
|
||||
});
|
||||
connect(user, &User::avatarChanged, this, &UserListModel::avatarChanged);
|
||||
#endif
|
||||
}
|
||||
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
||||
@@ -187,6 +203,13 @@ void UserListModel::refreshAllUsers()
|
||||
Q_EMIT usersRefreshed();
|
||||
}
|
||||
|
||||
void UserListModel::avatarChanged(Quotient::User *user, const Quotient::Room *context)
|
||||
{
|
||||
if (context == m_currentRoom) {
|
||||
refresh(user, {AvatarRole});
|
||||
}
|
||||
}
|
||||
|
||||
int UserListModel::findUserPos(Quotient::User *user) const
|
||||
{
|
||||
return findUserPos(m_currentRoom->safeMemberName(user->id()));
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <room.h>
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QObject>
|
||||
#include <QPointer>
|
||||
@@ -12,38 +14,37 @@ class NeoChatRoom;
|
||||
namespace Quotient
|
||||
{
|
||||
class User;
|
||||
class Room;
|
||||
}
|
||||
|
||||
/**
|
||||
* @class UserListModel
|
||||
*
|
||||
* This class defines the model for listing the users in a room.
|
||||
*
|
||||
* As well as gathering all the users from a room, the model ensures that they are
|
||||
* sorted in alphabetical order.
|
||||
*
|
||||
* @sa NeoChatRoom
|
||||
*/
|
||||
class UserListModel : public QAbstractListModel
|
||||
class UserType : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The room that the model is getting its users from.
|
||||
*/
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum Types {
|
||||
Owner = 1,
|
||||
Admin,
|
||||
Moderator,
|
||||
Member,
|
||||
Muted,
|
||||
Custom,
|
||||
};
|
||||
Q_ENUM(Types)
|
||||
};
|
||||
|
||||
class UserListModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
|
||||
public:
|
||||
enum EventRoles {
|
||||
NameRole = Qt::UserRole + 1, /**< The user's display name in the current room. */
|
||||
UserIdRole, /**< Matrix ID of the user. */
|
||||
AvatarRole, /**< The source URL for the user's avatar in the current room. */
|
||||
ObjectRole, /**< The QObject for the user. */
|
||||
PowerLevelRole, /**< The user's power level in the current room. */
|
||||
PowerLevelStringRole, /**< The name of the user's power level in the current room. */
|
||||
NameRole = Qt::UserRole + 1,
|
||||
UserIdRole,
|
||||
AvatarRole,
|
||||
ObjectRole,
|
||||
PowerLevelRole,
|
||||
PowerLevelStringRole,
|
||||
};
|
||||
|
||||
UserListModel(QObject *parent = nullptr);
|
||||
@@ -51,30 +52,11 @@ public:
|
||||
[[nodiscard]] NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
/**
|
||||
* @brief The user at the given index of the model.
|
||||
*/
|
||||
[[nodiscard]] Quotient::User *userAt(QModelIndex index) 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;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
[[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override;
|
||||
|
||||
/**
|
||||
* @brief Returns a mapping from Role enum values to role names.
|
||||
*
|
||||
* @sa EventRoles, QAbstractItemModel::roleNames()
|
||||
*/
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_SIGNALS:
|
||||
@@ -84,8 +66,9 @@ Q_SIGNALS:
|
||||
private Q_SLOTS:
|
||||
void userAdded(Quotient::User *user);
|
||||
void userRemoved(Quotient::User *user);
|
||||
void refreshUser(Quotient::User *user, const QVector<int> &roles = {});
|
||||
void refreshAllUsers();
|
||||
void refresh(Quotient::User *user, const QVector<int> &roles = {});
|
||||
void refreshAll();
|
||||
void avatarChanged(Quotient::User *user, const Quotient::Room *context);
|
||||
|
||||
private:
|
||||
QPointer<NeoChatRoom> m_currentRoom;
|
||||
|
||||
@@ -8,60 +8,43 @@
|
||||
|
||||
struct KWebShortcutModelPrivate;
|
||||
|
||||
/**
|
||||
* @class KWebShortcutModel
|
||||
*
|
||||
* This class defines the model for listing web shortcuts for a specified selectedText.
|
||||
*
|
||||
* This can be used as follows in your QML code:
|
||||
*
|
||||
* ```qml
|
||||
* QQC2.Menu {
|
||||
* id: webshortcutmenu
|
||||
*
|
||||
* title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
|
||||
* property bool isVisible: selectedText && selectedText.length > 0 && webshortcutmodel.enabled
|
||||
* Component.onCompleted: webshortcutmenu.parent.visible = isVisible
|
||||
* onIsVisibleChanged: webshortcutmenu.parent.visible = isVisible
|
||||
* Instantiator {
|
||||
* model: WebShortcutModel {
|
||||
* id: webshortcutmodel
|
||||
* selectedText: loadRoot.selectedText
|
||||
* onOpenUrl: Qt.openUrlExternally(url)
|
||||
* }
|
||||
* delegate: QQC2.MenuItem {
|
||||
* text: model.display
|
||||
* icon.name: model.decoration
|
||||
* onTriggered: webshortcutmodel.trigger(model.edit)
|
||||
* }
|
||||
* onObjectAdded: webshortcutmenu.insertItem(0, object)
|
||||
* }
|
||||
* QQC2.MenuSeparator {}
|
||||
* QQC2.MenuItem {
|
||||
* text: i18n("Configure Web Shortcuts...")
|
||||
* icon.name: "configure"
|
||||
* onTriggered: webshortcutmodel.configureWebShortcuts()
|
||||
* }
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
//! List model of web shortcuts for a a specified selectedText.
|
||||
//!
|
||||
//! This can be used as following in your QML code
|
||||
//!
|
||||
//! ```qml
|
||||
//! QQC2.Menu {
|
||||
//! id: webshortcutmenu
|
||||
//! title: i18n("Search for '%1'", webshortcutmodel.trunkatedSearchText)
|
||||
//! property bool isVisible: selectedText && selectedText.length > 0 && webshortcutmodel.enabled
|
||||
//! Component.onCompleted: webshortcutmenu.parent.visible = isVisible
|
||||
//! onIsVisibleChanged: webshortcutmenu.parent.visible = isVisible
|
||||
//! Instantiator {
|
||||
//! model: WebShortcutModel {
|
||||
//! id: webshortcutmodel
|
||||
//! selectedText: loadRoot.selectedText
|
||||
//! onOpenUrl: Qt.openUrlExternally(url)
|
||||
//! }
|
||||
//! delegate: QQC2.MenuItem {
|
||||
//! text: model.display
|
||||
//! icon.name: model.decoration
|
||||
//! onTriggered: webshortcutmodel.trigger(model.edit)
|
||||
//! }
|
||||
//! onObjectAdded: webshortcutmenu.insertItem(0, object)
|
||||
//! }
|
||||
//! QQC2.MenuSeparator {}
|
||||
//! QQC2.MenuItem {
|
||||
//! text: i18n("Configure Web Shortcuts...")
|
||||
//! icon.name: "configure"
|
||||
//! onTriggered: webshortcutmodel.configureWebShortcuts()
|
||||
//! }
|
||||
//! }
|
||||
//! ```
|
||||
class KWebShortcutModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The text to find web shortcuts for.
|
||||
*/
|
||||
Q_PROPERTY(QString selectedText READ selectedText WRITE setSelectedText NOTIFY selectedTextChanged)
|
||||
|
||||
/**
|
||||
* @brief The selectedText elided at a set width.
|
||||
*/
|
||||
Q_PROPERTY(QString trunkatedSearchText READ trunkatedSearchText NOTIFY selectedTextChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether web shortcuts are available.
|
||||
*/
|
||||
Q_PROPERTY(bool enabled READ enabled CONSTANT)
|
||||
public:
|
||||
explicit KWebShortcutModel(QObject *parent = nullptr);
|
||||
@@ -69,35 +52,13 @@ public:
|
||||
|
||||
QString selectedText() const;
|
||||
void setSelectedText(const QString &selectedText);
|
||||
|
||||
QString trunkatedSearchText() const;
|
||||
|
||||
bool enabled() const;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
*
|
||||
* @sa QAbstractItemModel::rowCount
|
||||
*/
|
||||
int rowCount(const QModelIndex &parent) const override;
|
||||
|
||||
/**
|
||||
* @brief Trigger the openUrl signal for the given web shortcut.
|
||||
*/
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
Q_INVOKABLE void trigger(const QString &data);
|
||||
|
||||
/**
|
||||
* @brief Request the menu for configuring web shortcut settings be opened.
|
||||
*/
|
||||
Q_INVOKABLE void configureWebShortcuts();
|
||||
|
||||
Q_SIGNALS:
|
||||
void selectedTextChanged();
|
||||
void openUrl(const QUrl &url);
|
||||
|
||||
@@ -33,23 +33,21 @@
|
||||
#include <events/simplestateevents.h>
|
||||
#include <jobs/downloadfilejob.h>
|
||||
#ifndef QUOTIENT_07
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
|
||||
#include <joinstate.h>
|
||||
#endif
|
||||
#endif
|
||||
#include <qt_connection_util.h>
|
||||
|
||||
#include "controller.h"
|
||||
#include "events/joinrulesevent.h"
|
||||
#include "joinrulesevent.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatuser.h"
|
||||
#include "notificationsmanager.h"
|
||||
#ifdef QUOTIENT_07
|
||||
#include "events/pollevent.h"
|
||||
#include "pollevent.h"
|
||||
#include "pollhandler.h"
|
||||
#endif
|
||||
#include "events/stickerevent.h"
|
||||
#include "filetransferpseudojob.h"
|
||||
#include "stickerevent.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
@@ -922,9 +920,6 @@ void NeoChatRoom::toggleReaction(const QString &eventId, const QString &reaction
|
||||
|
||||
bool NeoChatRoom::containsUser(const QString &userID) const
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
return memberState(userID) != Membership::Leave;
|
||||
#else
|
||||
auto u = Room::user(userID);
|
||||
|
||||
if (!u) {
|
||||
@@ -932,7 +927,6 @@ bool NeoChatRoom::containsUser(const QString &userID) const
|
||||
}
|
||||
|
||||
return Room::memberJoinState(u) != JoinState::Leave;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool NeoChatRoom::canSendEvent(const QString &eventType) const
|
||||
@@ -1914,30 +1908,3 @@ void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)
|
||||
};
|
||||
postJson("m.room.message", content);
|
||||
}
|
||||
|
||||
QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType)
|
||||
{
|
||||
return QJsonDocument(accountData(eventType)->fullJson()).toJson();
|
||||
}
|
||||
|
||||
QUrl NeoChatRoom::avatarForMember(NeoChatUser *user) const
|
||||
{
|
||||
#ifdef QUOTIENT_07
|
||||
const auto &url = memberAvatarUrl(user->id());
|
||||
if (url.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
auto avatar = connection()->makeMediaUrl(url);
|
||||
if (avatar.isValid() && avatar.scheme() == QStringLiteral("mxc")) {
|
||||
return avatar;
|
||||
} else {
|
||||
return QUrl();
|
||||
}
|
||||
#else
|
||||
QUrl url(QStringLiteral("mxc://%1").arg(user->avatarMediaId()));
|
||||
QUrlQuery q(url.query());
|
||||
q.addQueryItem(QStringLiteral("user_id"), user->id());
|
||||
url.setQuery(q);
|
||||
return url;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -749,13 +749,6 @@ public:
|
||||
Q_INVOKABLE PollHandler *poll(const QString &eventId);
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Get the full Json data for a given room account data event.
|
||||
*/
|
||||
Q_INVOKABLE QByteArray roomAcountDataJson(const QString &eventType);
|
||||
|
||||
Q_INVOKABLE [[nodiscard]] QUrl avatarForMember(NeoChatUser *user) const;
|
||||
|
||||
private:
|
||||
QSet<const Quotient::RoomEvent *> highlights;
|
||||
|
||||
|
||||
@@ -7,20 +7,9 @@
|
||||
|
||||
#include <user.h>
|
||||
|
||||
/**
|
||||
* @class NeoChatUser
|
||||
*
|
||||
* A class inherited from Quotient::User to add a user color function.
|
||||
*
|
||||
* @sa Quotient::User
|
||||
*/
|
||||
class NeoChatUser : public Quotient::User
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The color to use for the user.
|
||||
*/
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||
public:
|
||||
NeoChatUser(QString userId, Quotient::Connection *connection);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user