Compare commits
100 Commits
v22.11
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b1b21dd07 | ||
|
|
85a562d469 | ||
|
|
f50c62ba12 | ||
|
|
13f05a0995 | ||
|
|
1adddcc0d9 | ||
|
|
f03cd3f4c6 | ||
|
|
29a2e4eb99 | ||
|
|
7137a5808f | ||
|
|
fa6f451e11 | ||
|
|
666f247185 | ||
|
|
93dd25f954 | ||
|
|
8e2ba9552f | ||
|
|
1f02c5ea5e | ||
|
|
112b26c39d | ||
|
|
5e243ed01b | ||
|
|
706809d12a | ||
|
|
3fc06c2a74 | ||
|
|
4bc2c42982 | ||
|
|
6651fa4fa3 | ||
|
|
07d65a0046 | ||
|
|
4c9f062a70 | ||
|
|
893ee4a763 | ||
|
|
fa67d174d2 | ||
|
|
bb542521fb | ||
|
|
85d3cf2d77 | ||
|
|
ebf4cfb825 | ||
|
|
d7daa697df | ||
|
|
884484922d | ||
|
|
83ab751d4a | ||
|
|
fcee5bfa92 | ||
|
|
eb610ffe81 | ||
|
|
647cc25e57 | ||
|
|
3912b8e096 | ||
|
|
67f88416f1 | ||
|
|
8e3398df34 | ||
|
|
1a09405829 | ||
|
|
304054a4bb | ||
|
|
3c33bea7db | ||
|
|
15a7cc6e08 | ||
|
|
19b530d34b | ||
|
|
7fe8bc9f3b | ||
|
|
422fca4dc9 | ||
|
|
142dbe2c2c | ||
|
|
dced8ace1d | ||
|
|
de1fa71d8c | ||
|
|
158c99daef | ||
|
|
51e0023384 | ||
|
|
7f27056a34 | ||
|
|
cdbf5ea8e7 | ||
|
|
8bbd5e5a88 | ||
|
|
2e3c2c2424 | ||
|
|
80faa4bd4f | ||
|
|
de6f93b200 | ||
|
|
c46bfe05c1 | ||
|
|
0d50af6285 | ||
|
|
efb9ca5ac8 | ||
|
|
3ad5b62e27 | ||
|
|
8985aadcf1 | ||
|
|
907d52d693 | ||
|
|
eb5523a69c | ||
|
|
f475965cf7 | ||
|
|
1176cf029b | ||
|
|
d68fb81bcf | ||
|
|
81f7afe730 | ||
|
|
60e43a2794 | ||
|
|
76f686b580 | ||
|
|
cba4fdc397 | ||
|
|
62ea4bc67d | ||
|
|
25c7b7b780 | ||
|
|
6b3f44e923 | ||
|
|
9334585e0f | ||
|
|
1190511b54 | ||
|
|
b40d51841e | ||
|
|
f2ddee09c0 | ||
|
|
698cbceda3 | ||
|
|
66b1499fad | ||
|
|
e8748ce733 | ||
|
|
4bfa9c783c | ||
|
|
5cdfa086b2 | ||
|
|
e8824edfd4 | ||
|
|
507bd44bbf | ||
|
|
bfa08d178f | ||
|
|
9e01c96476 | ||
|
|
fe855f16f8 | ||
|
|
0fbc1b2121 | ||
|
|
b5d8acf9de | ||
|
|
1ab5bdb600 | ||
|
|
9060de1d60 | ||
|
|
1f83ab4450 | ||
|
|
e5680da5ce | ||
|
|
66bfcd6239 | ||
|
|
8f19c73908 | ||
|
|
716616210f | ||
|
|
cc414f71f4 | ||
|
|
d107dfcab1 | ||
|
|
875c03a0f6 | ||
|
|
4145987c65 | ||
|
|
232f3f624b | ||
|
|
2a2791c37f | ||
|
|
f1c9f5902a |
@@ -15,6 +15,10 @@ Dependencies:
|
|||||||
'libraries/kquickimageeditor': '@stable'
|
'libraries/kquickimageeditor': '@stable'
|
||||||
'frameworks/sonnet': '@stable'
|
'frameworks/sonnet': '@stable'
|
||||||
'libraries/kirigami-addons': '@latest'
|
'libraries/kirigami-addons': '@latest'
|
||||||
|
'third-party/libquotient': '@latest'
|
||||||
|
'third-party/qtkeychain': '@latest'
|
||||||
|
'third-party/cmark': '@latest'
|
||||||
|
'third-party/qcoro': '@latest'
|
||||||
- 'on': ['Windows', 'Linux', 'FreeBSD']
|
- 'on': ['Windows', 'Linux', 'FreeBSD']
|
||||||
'require':
|
'require':
|
||||||
'frameworks/qqc2-desktop-style': '@stable'
|
'frameworks/qqc2-desktop-style': '@stable'
|
||||||
|
|||||||
@@ -112,10 +112,7 @@ set_package_properties(KQuickImageEditor PROPERTIES
|
|||||||
PURPOSE "Add image editing capability to image attachments"
|
PURPOSE "Add image editing capability to image attachments"
|
||||||
)
|
)
|
||||||
|
|
||||||
find_package(QCoro${QT_MAJOR_VERSION} COMPONENTS Core QUIET)
|
find_package(QCoro${QT_MAJOR_VERSION} 0.4 COMPONENTS Core REQUIRED)
|
||||||
if(NOT QCoro${QT_MAJOR_VERSION}_FOUND)
|
|
||||||
find_package(QCoro REQUIRED)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
qcoro_enable_coroutines()
|
qcoro_enable_coroutines()
|
||||||
|
|
||||||
|
|||||||
@@ -5,9 +5,7 @@
|
|||||||
#include <QSignalSpy>
|
#include <QSignalSpy>
|
||||||
#include <QTest>
|
#include <QTest>
|
||||||
|
|
||||||
#define protected public // please don't hate me
|
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#undef protected
|
|
||||||
|
|
||||||
#include <connection.h>
|
#include <connection.h>
|
||||||
#include <quotient_common.h>
|
#include <quotient_common.h>
|
||||||
@@ -15,12 +13,23 @@
|
|||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
|
class TestRoom : public NeoChatRoom
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
using NeoChatRoom::NeoChatRoom;
|
||||||
|
|
||||||
|
void update(SyncRoomData &&data, bool fromCache = false)
|
||||||
|
{
|
||||||
|
Room::updateData(std::move(data), fromCache);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
class NeoChatRoomTest : public QObject {
|
class NeoChatRoomTest : public QObject {
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Connection *connection = nullptr;
|
Connection *connection = nullptr;
|
||||||
NeoChatRoom *room = nullptr;
|
TestRoom *room = nullptr;
|
||||||
|
|
||||||
private Q_SLOTS:
|
private Q_SLOTS:
|
||||||
void initTestCase();
|
void initTestCase();
|
||||||
@@ -31,7 +40,7 @@ private Q_SLOTS:
|
|||||||
void NeoChatRoomTest::initTestCase()
|
void NeoChatRoomTest::initTestCase()
|
||||||
{
|
{
|
||||||
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
connection = Connection::makeMockConnection(QStringLiteral("@bob:kde.org"));
|
||||||
room = new NeoChatRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
|
room = new TestRoom(connection, QStringLiteral("#myroom:kde.org"), JoinState::Join);
|
||||||
|
|
||||||
auto json = QJsonDocument::fromJson(R"EVENT({
|
auto json = QJsonDocument::fromJson(R"EVENT({
|
||||||
"account_data": {
|
"account_data": {
|
||||||
@@ -121,7 +130,7 @@ void NeoChatRoomTest::initTestCase()
|
|||||||
}
|
}
|
||||||
})EVENT");
|
})EVENT");
|
||||||
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
|
SyncRoomData roomData(QStringLiteral("@bob:kde.org"), JoinState::Join, json.object());
|
||||||
room->updateData(std::move(roomData));
|
room->update(std::move(roomData));
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoomTest::subtitleTextTest()
|
void NeoChatRoomTest::subtitleTextTest()
|
||||||
|
|||||||
BIN
icons/150-apps-neochat.png
Normal file
BIN
icons/150-apps-neochat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 5.5 KiB |
BIN
icons/44-apps-neochat.png
Normal file
BIN
icons/44-apps-neochat.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.5 KiB |
@@ -16,6 +16,7 @@
|
|||||||
<name xml:lang="ca-valencia">NeoChat</name>
|
<name xml:lang="ca-valencia">NeoChat</name>
|
||||||
<name xml:lang="cs">NeoChat</name>
|
<name xml:lang="cs">NeoChat</name>
|
||||||
<name xml:lang="de">NeoChat</name>
|
<name xml:lang="de">NeoChat</name>
|
||||||
|
<name xml:lang="el">NeoChat</name>
|
||||||
<name xml:lang="en-GB">NeoChat</name>
|
<name xml:lang="en-GB">NeoChat</name>
|
||||||
<name xml:lang="es">NeoChat</name>
|
<name xml:lang="es">NeoChat</name>
|
||||||
<name xml:lang="eu">NeoChat</name>
|
<name xml:lang="eu">NeoChat</name>
|
||||||
@@ -34,6 +35,7 @@
|
|||||||
<name xml:lang="pl">NeoChat</name>
|
<name xml:lang="pl">NeoChat</name>
|
||||||
<name xml:lang="pt">NeoChat</name>
|
<name xml:lang="pt">NeoChat</name>
|
||||||
<name xml:lang="pt-BR">NeoChat</name>
|
<name xml:lang="pt-BR">NeoChat</name>
|
||||||
|
<name xml:lang="ru">NeoChat</name>
|
||||||
<name xml:lang="sk">NeoChat</name>
|
<name xml:lang="sk">NeoChat</name>
|
||||||
<name xml:lang="sl">NeoChat</name>
|
<name xml:lang="sl">NeoChat</name>
|
||||||
<name xml:lang="sv">NeoChat</name>
|
<name xml:lang="sv">NeoChat</name>
|
||||||
@@ -49,6 +51,7 @@
|
|||||||
<summary xml:lang="ca-valencia">Un client per a 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="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="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
|
||||||
|
<summary xml:lang="el">Ένας πελάτης για το Matrix, το αποκεντρωμένο πρωτόκολλο επικοινωνίας</summary>
|
||||||
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
|
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
|
||||||
<summary xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado</summary>
|
<summary xml:lang="es">Un cliente para Matrix, el protocolo de comunicaciones descentralizado</summary>
|
||||||
<summary xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat</summary>
|
<summary xml:lang="eu">Matrix, deszentralizatutako komunikazio protokolorako bezero bat</summary>
|
||||||
@@ -67,6 +70,7 @@
|
|||||||
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
|
<summary xml:lang="pl">Program do obsługi matriksa, rozproszonego protokołu porozumiewania się</summary>
|
||||||
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
|
<summary xml:lang="pt">Um cliente para o Matrix, o protocolo de comunicação descentralizado</summary>
|
||||||
<summary xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary>
|
<summary xml:lang="pt-BR">Um cliente do Matrix, o protocolo de comunicação descentralizado</summary>
|
||||||
|
<summary xml:lang="ru">Клиент для Matrix — децентрализованного коммуникационного протокола</summary>
|
||||||
<summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary>
|
<summary xml:lang="sk">Klient pre matrix, decentralizovaný komunikačný protokol</summary>
|
||||||
<summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
|
<summary xml:lang="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
|
||||||
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
|
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
|
||||||
@@ -81,6 +85,7 @@
|
|||||||
<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">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="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="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="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="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="eu">NeoChat Matrix bezero bat da. Familiari, lankideei eta lagunei testu-mezuak, bideoak eta audio-fitxategiak bidaltzeko aukera ematen du, Matrix protokoloa erabiliz.</p>
|
||||||
@@ -97,6 +102,7 @@
|
|||||||
<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="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">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="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="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="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="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>
|
||||||
@@ -110,6 +116,7 @@
|
|||||||
<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">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="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="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="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="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="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>
|
||||||
@@ -126,6 +133,7 @@
|
|||||||
<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="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">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="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="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="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="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>
|
||||||
@@ -138,7 +146,8 @@
|
|||||||
<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="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">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="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 dem Mobiltelefon als auch auf dem Arbeitsfläche und bietet ein einheitliches Benutzererlebnis. </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="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="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="eu">NeoChat mugikorretan eta mahaigainean dabil, erabiltzaile esperientzia koherentea eskainiz.</p>
|
||||||
@@ -155,6 +164,7 @@
|
|||||||
<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="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">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="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="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="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="sv">NeoChat fungerar både på mobil och skrivbord och tillhandahåller en konsekvent användarupplevelse.</p>
|
||||||
@@ -175,6 +185,7 @@
|
|||||||
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
|
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
|
||||||
<developer_name xml:lang="cs">Komunita KDE</developer_name>
|
<developer_name xml:lang="cs">Komunita KDE</developer_name>
|
||||||
<developer_name xml:lang="de">Die KDE-Gemeinschaft</developer_name>
|
<developer_name xml:lang="de">Die KDE-Gemeinschaft</developer_name>
|
||||||
|
<developer_name xml:lang="el">Η Κοινότητα του KDE</developer_name>
|
||||||
<developer_name xml:lang="en-GB">The KDE Community</developer_name>
|
<developer_name xml:lang="en-GB">The KDE Community</developer_name>
|
||||||
<developer_name xml:lang="es">La comunidad KDE</developer_name>
|
<developer_name xml:lang="es">La comunidad KDE</developer_name>
|
||||||
<developer_name xml:lang="eu">KDE komunitatea</developer_name>
|
<developer_name xml:lang="eu">KDE komunitatea</developer_name>
|
||||||
@@ -193,6 +204,7 @@
|
|||||||
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
|
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
|
||||||
<developer_name xml:lang="pt">A Comunidade do KDE</developer_name>
|
<developer_name xml:lang="pt">A Comunidade do KDE</developer_name>
|
||||||
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
|
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
|
||||||
|
<developer_name xml:lang="ru">Сообщество KDE</developer_name>
|
||||||
<developer_name xml:lang="sk">KDE Komunita</developer_name>
|
<developer_name xml:lang="sk">KDE Komunita</developer_name>
|
||||||
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
|
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
|
||||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||||
@@ -204,6 +216,7 @@
|
|||||||
<project_license>GPL-3.0</project_license>
|
<project_license>GPL-3.0</project_license>
|
||||||
<custom>
|
<custom>
|
||||||
<value key="KDE::matrix">#neochat:kde.org</value>
|
<value key="KDE::matrix">#neochat:kde.org</value>
|
||||||
|
<value key="KDE::windows_store">https://www.microsoft.com/store/apps/9PNXWVNRC29H</value>
|
||||||
</custom>
|
</custom>
|
||||||
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
|
||||||
<screenshots>
|
<screenshots>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ Name[ca]=NeoChat
|
|||||||
Name[ca@valencia]=NeoChat
|
Name[ca@valencia]=NeoChat
|
||||||
Name[cs]=NeoChat
|
Name[cs]=NeoChat
|
||||||
Name[de]=NeoChat
|
Name[de]=NeoChat
|
||||||
|
Name[el]=NeoChat
|
||||||
Name[en_GB]=NeoChat
|
Name[en_GB]=NeoChat
|
||||||
Name[es]=NeoChat
|
Name[es]=NeoChat
|
||||||
Name[eu]=NeoChat
|
Name[eu]=NeoChat
|
||||||
@@ -29,6 +30,7 @@ Name[pl]=NeoChat
|
|||||||
Name[pt]=NeoChat
|
Name[pt]=NeoChat
|
||||||
Name[pt_BR]=NeoChat
|
Name[pt_BR]=NeoChat
|
||||||
Name[ro]=NeoChat
|
Name[ro]=NeoChat
|
||||||
|
Name[ru]=NeoChat
|
||||||
Name[sk]=NeoChat
|
Name[sk]=NeoChat
|
||||||
Name[sl]=NeoChat
|
Name[sl]=NeoChat
|
||||||
Name[sv]=NeoChat
|
Name[sv]=NeoChat
|
||||||
@@ -44,6 +46,7 @@ GenericName[ca]=Client de Matrix
|
|||||||
GenericName[ca@valencia]=Client de Matrix
|
GenericName[ca@valencia]=Client de Matrix
|
||||||
GenericName[cs]=Klient protokolu Matrix
|
GenericName[cs]=Klient protokolu Matrix
|
||||||
GenericName[de]=Matrix-Programm
|
GenericName[de]=Matrix-Programm
|
||||||
|
GenericName[el]=Εφαρμογή του Matrix
|
||||||
GenericName[en_GB]=Matrix Client
|
GenericName[en_GB]=Matrix Client
|
||||||
GenericName[es]=Cliente para Matrix
|
GenericName[es]=Cliente para Matrix
|
||||||
GenericName[eu]=Matrix bezeroa
|
GenericName[eu]=Matrix bezeroa
|
||||||
@@ -64,6 +67,7 @@ GenericName[pl]=Program Matriksa
|
|||||||
GenericName[pt]=Cliente de Matrix
|
GenericName[pt]=Cliente de Matrix
|
||||||
GenericName[pt_BR]=Cliente Matrix
|
GenericName[pt_BR]=Cliente Matrix
|
||||||
GenericName[ro]=Client Matrix
|
GenericName[ro]=Client Matrix
|
||||||
|
GenericName[ru]=Клиент Matrix
|
||||||
GenericName[sk]=Matrix Client
|
GenericName[sk]=Matrix Client
|
||||||
GenericName[sl]=Odjemalec Matrix
|
GenericName[sl]=Odjemalec Matrix
|
||||||
GenericName[sv]=Matrix-klient
|
GenericName[sv]=Matrix-klient
|
||||||
@@ -78,6 +82,7 @@ Comment[az]=Matrix protokolu üçün müştəri
|
|||||||
Comment[ca]=Client per al protocol Matrix
|
Comment[ca]=Client per al protocol Matrix
|
||||||
Comment[ca@valencia]=Client per al protocol Matrix
|
Comment[ca@valencia]=Client per al protocol Matrix
|
||||||
Comment[de]=Programm für das Matrix-Protokoll
|
Comment[de]=Programm für das Matrix-Protokoll
|
||||||
|
Comment[el]=Πελάτης για το πρωτόκολλο Matrix
|
||||||
Comment[en_GB]=Client for the Matrix protocol
|
Comment[en_GB]=Client for the Matrix protocol
|
||||||
Comment[es]=Cliente para el protocolo Matrix
|
Comment[es]=Cliente para el protocolo Matrix
|
||||||
Comment[eu]=Matrix protokolorako bezeroa
|
Comment[eu]=Matrix protokolorako bezeroa
|
||||||
@@ -98,6 +103,7 @@ Comment[pl]=Program obsługi protokołu Matriksa
|
|||||||
Comment[pt]=Cliente para o protocolo Matrix
|
Comment[pt]=Cliente para o protocolo Matrix
|
||||||
Comment[pt_BR]=Cliente para o protocolo Matrix
|
Comment[pt_BR]=Cliente para o protocolo Matrix
|
||||||
Comment[ro]=Client pentru protocolul Matrix
|
Comment[ro]=Client pentru protocolul Matrix
|
||||||
|
Comment[ru]=Клиент для протокола Matrix
|
||||||
Comment[sk]=Klient protokolu Matrix
|
Comment[sk]=Klient protokolu Matrix
|
||||||
Comment[sl]=Odjemalec za protokol Matrix
|
Comment[sl]=Odjemalec za protokol Matrix
|
||||||
Comment[sv]=Klient för protokollet Matrix
|
Comment[sv]=Klient för protokollet Matrix
|
||||||
|
|||||||
757
po/ar/neochat.po
757
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
676
po/az/neochat.po
676
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
745
po/ca/neochat.po
745
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1144
po/cs/neochat.po
1144
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
662
po/da/neochat.po
662
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
1715
po/de/neochat.po
1715
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
3383
po/el/neochat.po
Normal file
3383
po/el/neochat.po
Normal file
File diff suppressed because it is too large
Load Diff
1582
po/en_GB/neochat.po
1582
po/en_GB/neochat.po
File diff suppressed because it is too large
Load Diff
746
po/es/neochat.po
746
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
750
po/eu/neochat.po
750
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
676
po/fi/neochat.po
676
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
1052
po/fr/neochat.po
1052
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
667
po/hu/neochat.po
667
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
729
po/ia/neochat.po
729
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
932
po/id/neochat.po
932
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
676
po/ie/neochat.po
676
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
122
po/it/docs/neochat/man-neochat.1.docbook
Normal file
122
po/it/docs/neochat/man-neochat.1.docbook
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
<?xml version="1.0" ?>
|
||||||
|
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
|
||||||
|
<!ENTITY % Italian "INCLUDE">
|
||||||
|
]>
|
||||||
|
<!--
|
||||||
|
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
|
||||||
|
SPDX-License-Identifier: CC-BY-SA-4.0
|
||||||
|
-->
|
||||||
|
|
||||||
|
<refentry lang="&language;">
|
||||||
|
<refentryinfo>
|
||||||
|
<title
|
||||||
|
>Manuale utente di NeoChat</title>
|
||||||
|
<author
|
||||||
|
><firstname
|
||||||
|
>Carl</firstname
|
||||||
|
><surname
|
||||||
|
>Schwan</surname
|
||||||
|
> <contrib
|
||||||
|
>Pagina man di NeoChat.</contrib
|
||||||
|
> <email
|
||||||
|
>carl@carlschwan.eu</email
|
||||||
|
></author>
|
||||||
|
<date
|
||||||
|
>1/11/2022</date>
|
||||||
|
<releaseinfo
|
||||||
|
>22.09</releaseinfo>
|
||||||
|
<productname
|
||||||
|
>NeoChat</productname>
|
||||||
|
</refentryinfo>
|
||||||
|
|
||||||
|
<refmeta>
|
||||||
|
<refentrytitle>
|
||||||
|
<command
|
||||||
|
>neochat</command>
|
||||||
|
</refentrytitle>
|
||||||
|
<manvolnum
|
||||||
|
>1</manvolnum>
|
||||||
|
</refmeta>
|
||||||
|
|
||||||
|
<refnamediv>
|
||||||
|
<refname
|
||||||
|
>neochat</refname>
|
||||||
|
<refpurpose
|
||||||
|
>Client per l'interazione con il protocollo di messaggistica matrix</refpurpose>
|
||||||
|
</refnamediv>
|
||||||
|
<!-- body begins here -->
|
||||||
|
<refsynopsisdiv id='synopsis'>
|
||||||
|
<cmdsynopsis
|
||||||
|
><command
|
||||||
|
>neochat</command
|
||||||
|
> <arg choice="opt"
|
||||||
|
><replaceable
|
||||||
|
>URI</replaceable
|
||||||
|
></arg
|
||||||
|
> </cmdsynopsis>
|
||||||
|
</refsynopsisdiv>
|
||||||
|
|
||||||
|
|
||||||
|
<refsect1 id="description">
|
||||||
|
<title
|
||||||
|
>Descrizione</title>
|
||||||
|
<para
|
||||||
|
><command
|
||||||
|
>neochat</command
|
||||||
|
> è un'applicazione di chat per il protocollo Matrix che funziona sia su desktop che su dispositivi mobili. </para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id="options"
|
||||||
|
><title
|
||||||
|
>Opzioni</title>
|
||||||
|
<variablelist>
|
||||||
|
<varlistentry>
|
||||||
|
<term
|
||||||
|
><option
|
||||||
|
>URI</option
|
||||||
|
></term>
|
||||||
|
<listitem>
|
||||||
|
<para
|
||||||
|
>L'URI matrix per un utente o una stanza. ad esempio matrix:u/utente:esempio.org e matrix:r/stanza:esempio.org. Questo farà in modo che NeoChat provi ad aprire la stanza o la conversazione specificata. </para>
|
||||||
|
</listitem>
|
||||||
|
</varlistentry>
|
||||||
|
</variablelist>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id="bug">
|
||||||
|
<title
|
||||||
|
>Segnalazione bug</title>
|
||||||
|
<para
|
||||||
|
>Puoi segnalare bug e richiedere funzionalità su <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&component=General"
|
||||||
|
>https://bugs.kde.org/enter_bug.cgi? product=NeoChat&component=General</ulink
|
||||||
|
></para>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1>
|
||||||
|
<title
|
||||||
|
>Vedi anche</title>
|
||||||
|
<simplelist>
|
||||||
|
<member
|
||||||
|
>Un elenco di domande frequenti su Matrix <ulink url="https://matrix.org/faq/"
|
||||||
|
>https://matrix.org/faq/</ulink
|
||||||
|
> </member>
|
||||||
|
<member
|
||||||
|
>kf5options(7)</member>
|
||||||
|
<member
|
||||||
|
>qt5options(7)</member>
|
||||||
|
</simplelist>
|
||||||
|
</refsect1>
|
||||||
|
|
||||||
|
<refsect1 id="copyright"
|
||||||
|
><title
|
||||||
|
>Copyright</title>
|
||||||
|
<para
|
||||||
|
>Copyright © 2020-2022 Tobias Fella </para>
|
||||||
|
<para
|
||||||
|
>Copyright © 2020-2022 Carl Schwan </para>
|
||||||
|
<para
|
||||||
|
>Licenza: GNU General Public Version 3 o successiva <<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
|
||||||
|
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
|
||||||
|
>></para>
|
||||||
|
</refsect1>
|
||||||
|
</refentry>
|
||||||
1217
po/it/neochat.po
1217
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
696
po/ja/neochat.po
696
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
887
po/ka/neochat.po
887
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
676
po/ko/neochat.po
676
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
748
po/nl/neochat.po
748
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
988
po/nn/neochat.po
988
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
666
po/pa/neochat.po
666
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
1418
po/pl/neochat.po
1418
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
753
po/pt/neochat.po
753
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
2104
po/ru/neochat.po
2104
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
666
po/sk/neochat.po
666
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
772
po/sl/neochat.po
772
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
692
po/sv/neochat.po
692
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
968
po/ta/neochat.po
968
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
749
po/tr/neochat.po
749
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
745
po/uk/neochat.po
745
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
1270
po/zh_CN/neochat.po
1270
po/zh_CN/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -7,6 +7,7 @@ add_library(neochat STATIC
|
|||||||
controller.cpp
|
controller.cpp
|
||||||
actionshandler.cpp
|
actionshandler.cpp
|
||||||
emojimodel.cpp
|
emojimodel.cpp
|
||||||
|
emojitones.cpp
|
||||||
customemojimodel.cpp
|
customemojimodel.cpp
|
||||||
clipboard.cpp
|
clipboard.cpp
|
||||||
matriximageprovider.cpp
|
matriximageprovider.cpp
|
||||||
@@ -19,6 +20,7 @@ add_library(neochat STATIC
|
|||||||
neochatroom.cpp
|
neochatroom.cpp
|
||||||
neochatuser.cpp
|
neochatuser.cpp
|
||||||
userlistmodel.cpp
|
userlistmodel.cpp
|
||||||
|
userfiltermodel.cpp
|
||||||
publicroomlistmodel.cpp
|
publicroomlistmodel.cpp
|
||||||
userdirectorylistmodel.cpp
|
userdirectorylistmodel.cpp
|
||||||
keywordnotificationrulemodel.cpp
|
keywordnotificationrulemodel.cpp
|
||||||
@@ -44,6 +46,7 @@ add_library(neochat STATIC
|
|||||||
serverlistmodel.cpp
|
serverlistmodel.cpp
|
||||||
statemodel.cpp
|
statemodel.cpp
|
||||||
filetransferpseudojob.cpp
|
filetransferpseudojob.cpp
|
||||||
|
searchmodel.cpp
|
||||||
)
|
)
|
||||||
|
|
||||||
add_executable(neochat-app
|
add_executable(neochat-app
|
||||||
@@ -67,7 +70,7 @@ endif()
|
|||||||
|
|
||||||
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
|
||||||
|
|
||||||
target_sources(neochat PRIVATE ${NEOCHAT_ICON})
|
target_sources(neochat-app PRIVATE ${NEOCHAT_ICON})
|
||||||
|
|
||||||
if(NOT ANDROID)
|
if(NOT ANDROID)
|
||||||
target_sources(neochat PRIVATE colorschemer.cpp)
|
target_sources(neochat PRIVATE colorschemer.cpp)
|
||||||
@@ -90,12 +93,7 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
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 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::SonnetCore KF5::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES})
|
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF5::I18n KF5::Kirigami2 KF5::Notifications KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::SonnetCore KF5::ItemModels Quotient cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
||||||
if(TARGET QCoro5::Coro)
|
|
||||||
target_link_libraries(neochat PUBLIC QCoro5::Coro)
|
|
||||||
else()
|
|
||||||
target_link_libraries(neochat PUBLIC QCoro::QCoro)
|
|
||||||
endif()
|
|
||||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||||
|
|
||||||
if(NEOCHAT_FLATPAK)
|
if(NEOCHAT_FLATPAK)
|
||||||
@@ -161,12 +159,18 @@ if(ANDROID)
|
|||||||
"zoom-out"
|
"zoom-out"
|
||||||
"image-rotate-left-symbolic"
|
"image-rotate-left-symbolic"
|
||||||
"image-rotate-right-symbolic"
|
"image-rotate-right-symbolic"
|
||||||
"channel-insecure-symbolic"
|
"channel-secure-symbolic"
|
||||||
"download"
|
"download"
|
||||||
"smiley"
|
"smiley"
|
||||||
"tools-check-spelling"
|
"tools-check-spelling"
|
||||||
"username-copy"
|
"username-copy"
|
||||||
"edit-copy"
|
"system-switch-user"
|
||||||
|
"bookmark-new"
|
||||||
|
"bookmark-remove"
|
||||||
|
"favorite"
|
||||||
|
"window-new"
|
||||||
|
"globe"
|
||||||
|
"visibility"
|
||||||
)
|
)
|
||||||
else()
|
else()
|
||||||
target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets)
|
target_link_libraries(neochat PUBLIC Qt::Widgets KF5::KIOWidgets)
|
||||||
|
|||||||
@@ -17,9 +17,8 @@
|
|||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
#include "customemojimodel.h"
|
#include "customemojimodel.h"
|
||||||
#include "neochatconfig.h"
|
#include "neochatconfig.h"
|
||||||
#include "neochatroom.h"
|
|
||||||
#include "roommanager.h"
|
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
|
#include "roommanager.h"
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include <QQmlFile>
|
#include <QQmlFile>
|
||||||
#include <QQmlFileSelector>
|
#include <QQmlFileSelector>
|
||||||
#include <QQuickTextDocument>
|
|
||||||
#include <QStringBuilder>
|
#include <QStringBuilder>
|
||||||
#include <QSyntaxHighlighter>
|
#include <QSyntaxHighlighter>
|
||||||
#include <QTextBlock>
|
#include <QTextBlock>
|
||||||
@@ -16,7 +15,6 @@
|
|||||||
#include <Sonnet/Settings>
|
#include <Sonnet/Settings>
|
||||||
|
|
||||||
#include "actionsmodel.h"
|
#include "actionsmodel.h"
|
||||||
#include "completionmodel.h"
|
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roomlistmodel.h"
|
#include "roomlistmodel.h"
|
||||||
|
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ QString Clipboard::saveImage(QString localPath) const
|
|||||||
}
|
}
|
||||||
|
|
||||||
QDir dir;
|
QDir dir;
|
||||||
if (!dir.exists(localPath)) {
|
if (!dir.exists(QFileInfo(url.fileName()).absoluteFilePath())) {
|
||||||
dir.mkpath(localPath);
|
dir.mkpath(QFileInfo(url.fileName()).absoluteFilePath());
|
||||||
}
|
}
|
||||||
|
|
||||||
image.save(url.toLocalFile());
|
image.save(url.toLocalFile());
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
#include "collapsestateproxymodel.h"
|
#include "collapsestateproxymodel.h"
|
||||||
#include "messageeventmodel.h"
|
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,6 @@
|
|||||||
#include "customemojimodel.h"
|
#include "customemojimodel.h"
|
||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "roomlistmodel.h"
|
|
||||||
#include "userlistmodel.h"
|
#include "userlistmodel.h"
|
||||||
|
|
||||||
CompletionModel::CompletionModel(QObject *parent)
|
CompletionModel::CompletionModel(QObject *parent)
|
||||||
@@ -102,6 +101,9 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
|
|||||||
if (role == ReplacedText) {
|
if (role == ReplacedText) {
|
||||||
return m_filterModel->data(filterIndex, CustomEmojiModel::ReplacedTextRole);
|
return m_filterModel->data(filterIndex, CustomEmojiModel::ReplacedTextRole);
|
||||||
}
|
}
|
||||||
|
if (role == Subtitle) {
|
||||||
|
return m_filterModel->data(filterIndex, EmojiModel::DescriptionRole);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
@@ -149,7 +151,7 @@ void CompletionModel::updateCompletion()
|
|||||||
m_filterModel->setSourceModel(m_emojiModel);
|
m_filterModel->setSourceModel(m_emojiModel);
|
||||||
m_autoCompletionType = Emoji;
|
m_autoCompletionType = Emoji;
|
||||||
m_filterModel->setFilterRole(CustomEmojiModel::Name);
|
m_filterModel->setFilterRole(CustomEmojiModel::Name);
|
||||||
m_filterModel->setSecondaryFilterRole(-1);
|
m_filterModel->setSecondaryFilterRole(EmojiModel::DescriptionRole);
|
||||||
m_filterModel->setFullText(m_fullText);
|
m_filterModel->setFullText(m_fullText);
|
||||||
m_filterModel->setFilterText(m_text);
|
m_filterModel->setFilterText(m_text);
|
||||||
m_filterModel->invalidate();
|
m_filterModel->invalidate();
|
||||||
|
|||||||
@@ -25,6 +25,17 @@ bool CompletionProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &so
|
|||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool CompletionProxyModel::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const
|
||||||
|
{
|
||||||
|
if (m_secondaryFilterRole == -1)
|
||||||
|
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||||
|
bool left_primary = sourceModel()->data(source_left, filterRole()).toString().startsWith(m_filterText, Qt::CaseInsensitive);
|
||||||
|
bool right_primary = sourceModel()->data(source_right, filterRole()).toString().startsWith(m_filterText, Qt::CaseInsensitive);
|
||||||
|
if (left_primary != right_primary)
|
||||||
|
return left_primary;
|
||||||
|
return QSortFilterProxyModel::lessThan(source_left, source_right);
|
||||||
|
}
|
||||||
|
|
||||||
int CompletionProxyModel::secondaryFilterRole() const
|
int CompletionProxyModel::secondaryFilterRole() const
|
||||||
{
|
{
|
||||||
return m_secondaryFilterRole;
|
return m_secondaryFilterRole;
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ class CompletionProxyModel : public QSortFilterProxyModel
|
|||||||
|
|
||||||
public:
|
public:
|
||||||
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
bool filterAcceptsRow(int sourceRow, const QModelIndex &sourceParent) const override;
|
||||||
|
bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override;
|
||||||
|
|
||||||
int secondaryFilterRole() const;
|
int secondaryFilterRole() const;
|
||||||
void setSecondaryFilterRole(int role);
|
void setSecondaryFilterRole(int role);
|
||||||
|
|||||||
@@ -19,7 +19,6 @@
|
|||||||
#include <QGuiApplication>
|
#include <QGuiApplication>
|
||||||
#include <QImageReader>
|
#include <QImageReader>
|
||||||
#include <QNetworkProxy>
|
#include <QNetworkProxy>
|
||||||
#include <QQuickItem>
|
|
||||||
#include <QQuickTextDocument>
|
#include <QQuickTextDocument>
|
||||||
#include <QQuickWindow>
|
#include <QQuickWindow>
|
||||||
#include <QStandardPaths>
|
#include <QStandardPaths>
|
||||||
@@ -743,11 +742,15 @@ void Controller::setApplicationProxy()
|
|||||||
proxy.setType(QNetworkProxy::HttpProxy);
|
proxy.setType(QNetworkProxy::HttpProxy);
|
||||||
proxy.setHostName(cfg->proxyHost());
|
proxy.setHostName(cfg->proxyHost());
|
||||||
proxy.setPort(cfg->proxyPort());
|
proxy.setPort(cfg->proxyPort());
|
||||||
|
proxy.setUser(cfg->proxyUser());
|
||||||
|
proxy.setPassword(cfg->proxyPassword());
|
||||||
break;
|
break;
|
||||||
case 2: // SOCKS 5
|
case 2: // SOCKS 5
|
||||||
proxy.setType(QNetworkProxy::Socks5Proxy);
|
proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||||
proxy.setHostName(cfg->proxyHost());
|
proxy.setHostName(cfg->proxyHost());
|
||||||
proxy.setPort(cfg->proxyPort());
|
proxy.setPort(cfg->proxyPort());
|
||||||
|
proxy.setUser(cfg->proxyUser());
|
||||||
|
proxy.setPassword(cfg->proxyPassword());
|
||||||
break;
|
break;
|
||||||
case 0: // System Default
|
case 0: // System Default
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -11,6 +11,10 @@ struct CustomEmoji {
|
|||||||
QString name; // with :semicolons:
|
QString name; // with :semicolons:
|
||||||
QString url; // mxc://
|
QString url; // mxc://
|
||||||
QRegularExpression regexp;
|
QRegularExpression regexp;
|
||||||
|
|
||||||
|
Q_GADGET
|
||||||
|
Q_PROPERTY(QString unicode MEMBER url)
|
||||||
|
Q_PROPERTY(QString name MEMBER name)
|
||||||
};
|
};
|
||||||
|
|
||||||
class CustomEmojiModel : public QAbstractListModel
|
class CustomEmojiModel : public QAbstractListModel
|
||||||
@@ -25,6 +29,7 @@ public:
|
|||||||
MxcUrl = 50,
|
MxcUrl = 50,
|
||||||
DisplayRole = 51,
|
DisplayRole = 51,
|
||||||
ReplacedTextRole = 52,
|
ReplacedTextRole = 52,
|
||||||
|
DescriptionRole = 53, // also invalid, reserved
|
||||||
};
|
};
|
||||||
Q_ENUM(Roles);
|
Q_ENUM(Roles);
|
||||||
|
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
#include <QVariant>
|
#include <QVariant>
|
||||||
|
|
||||||
#include "emojimodel.h"
|
#include "emojimodel.h"
|
||||||
|
#include "emojitones.h"
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include "customemojimodel.h"
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
#include <qnamespace.h>
|
|
||||||
|
|
||||||
EmojiModel::EmojiModel(QObject *parent)
|
EmojiModel::EmojiModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
@@ -48,6 +49,8 @@ QVariant EmojiModel::data(const QModelIndex &index, int role) const
|
|||||||
return QStringLiteral("invalid");
|
return QStringLiteral("invalid");
|
||||||
case DisplayRole:
|
case DisplayRole:
|
||||||
return QStringLiteral("%2 :%1:").arg(emoji.shortName, emoji.unicode);
|
return QStringLiteral("%2 :%1:").arg(emoji.shortName, emoji.unicode);
|
||||||
|
case DescriptionRole:
|
||||||
|
return emoji.description;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
@@ -58,16 +61,19 @@ QHash<int, QByteArray> EmojiModel::roleNames() const
|
|||||||
return {{ShortNameRole, "shortName"}, {UnicodeRole, "unicode"}};
|
return {{ShortNameRole, "shortName"}, {UnicodeRole, "unicode"}};
|
||||||
}
|
}
|
||||||
|
|
||||||
QMultiHash<QString, QVariant> EmojiModel::_tones = {
|
|
||||||
#include "emojitones.h"
|
|
||||||
};
|
|
||||||
|
|
||||||
QVariantList EmojiModel::history() const
|
QVariantList EmojiModel::history() const
|
||||||
{
|
{
|
||||||
return m_settings.value("Editor/emojis", QVariantList()).toList();
|
return m_settings.value("Editor/emojis", QVariantList()).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
|
QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
|
||||||
|
{
|
||||||
|
auto emojis = CustomEmojiModel::instance().filterModel(filter);
|
||||||
|
emojis += filterModelNoCustom(filter, limit);
|
||||||
|
return emojis;
|
||||||
|
}
|
||||||
|
|
||||||
|
QVariantList EmojiModel::filterModelNoCustom(const QString &filter, bool limit)
|
||||||
{
|
{
|
||||||
QVariantList result;
|
QVariantList result;
|
||||||
|
|
||||||
@@ -82,7 +88,6 @@ QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,12 +115,28 @@ QVariantList EmojiModel::emojis(Category category) const
|
|||||||
if (category == History) {
|
if (category == History) {
|
||||||
return history();
|
return history();
|
||||||
}
|
}
|
||||||
|
if (category == HistoryNoCustom) {
|
||||||
|
QVariantList list;
|
||||||
|
for (const auto &e : history()) {
|
||||||
|
auto emoji = qvariant_cast<Emoji>(e);
|
||||||
|
if (!emoji.isCustom) {
|
||||||
|
list.append(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
if (category == Custom) {
|
||||||
|
return CustomEmojiModel::instance().filterModel({});
|
||||||
|
}
|
||||||
return _emojis[category];
|
return _emojis[category];
|
||||||
}
|
}
|
||||||
|
|
||||||
QVariantList EmojiModel::tones(const QString &baseEmoji) const
|
QVariantList EmojiModel::tones(const QString &baseEmoji) const
|
||||||
{
|
{
|
||||||
return _tones.values(baseEmoji);
|
if (baseEmoji.endsWith("tone")) {
|
||||||
|
return EmojiTones::_tones.values(baseEmoji.split(":")[0]);
|
||||||
|
}
|
||||||
|
return EmojiTones::_tones.values(baseEmoji);
|
||||||
}
|
}
|
||||||
|
|
||||||
QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis;
|
QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis;
|
||||||
@@ -123,21 +144,11 @@ QHash<EmojiModel::Category, QVariantList> EmojiModel::_emojis;
|
|||||||
QVariantList EmojiModel::categories() const
|
QVariantList EmojiModel::categories() const
|
||||||
{
|
{
|
||||||
return QVariantList{
|
return QVariantList{
|
||||||
// {QVariantMap{
|
|
||||||
// {"category", EmojiModel::Search},
|
|
||||||
// {"name", i18nc("Search for emojis", "Search")},
|
|
||||||
// {"emoji", QStringLiteral("🔎")},
|
|
||||||
// }},
|
|
||||||
{QVariantMap{
|
{QVariantMap{
|
||||||
{"category", EmojiModel::History},
|
{"category", EmojiModel::HistoryNoCustom},
|
||||||
{"name", i18nc("Previously used emojis", "History")},
|
{"name", i18nc("Previously used emojis", "History")},
|
||||||
{"emoji", QStringLiteral("⌛️")},
|
{"emoji", QStringLiteral("⌛️")},
|
||||||
}},
|
}},
|
||||||
{QVariantMap{
|
|
||||||
{"category", EmojiModel::Custom},
|
|
||||||
{"name", i18nc("'Custom' is a category of emoji", "Custom")},
|
|
||||||
{"emoji", QStringLiteral("😏")},
|
|
||||||
}},
|
|
||||||
{QVariantMap{
|
{QVariantMap{
|
||||||
{"category", EmojiModel::Smileys},
|
{"category", EmojiModel::Smileys},
|
||||||
{"name", i18nc("'Smileys' is a category of emoji", "Smileys")},
|
{"name", i18nc("'Smileys' is a category of emoji", "Smileys")},
|
||||||
@@ -185,3 +196,23 @@ QVariantList EmojiModel::categories() const
|
|||||||
}},
|
}},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QVariantList EmojiModel::categoriesWithCustom() const
|
||||||
|
{
|
||||||
|
auto cats = categories();
|
||||||
|
cats.removeAt(0);
|
||||||
|
cats.insert(0,
|
||||||
|
QVariantMap{
|
||||||
|
{"category", EmojiModel::History},
|
||||||
|
{"name", i18nc("Previously used emojis", "History")},
|
||||||
|
{"emoji", QStringLiteral("⌛️")},
|
||||||
|
});
|
||||||
|
cats.insert(1,
|
||||||
|
QVariantMap{
|
||||||
|
{"category", EmojiModel::Custom},
|
||||||
|
{"name", i18nc("'Custom' is a category of emoji", "Custom")},
|
||||||
|
{"emoji", QStringLiteral("🖼️")},
|
||||||
|
});
|
||||||
|
;
|
||||||
|
return cats;
|
||||||
|
}
|
||||||
|
|||||||
@@ -8,12 +8,18 @@
|
|||||||
#include <QSettings>
|
#include <QSettings>
|
||||||
|
|
||||||
struct Emoji {
|
struct Emoji {
|
||||||
Emoji(QString u, QString s, bool isCustom = false)
|
Emoji(QString unicode, QString shortname, bool isCustom = false)
|
||||||
: unicode(std::move(std::move(u)))
|
: unicode(std::move(unicode))
|
||||||
, shortName(std::move(std::move(s)))
|
, shortName(std::move(shortname))
|
||||||
, isCustom(isCustom)
|
, isCustom(isCustom)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
Emoji(QString unicode, QString shortname, QString description)
|
||||||
|
: unicode(std::move(unicode))
|
||||||
|
, shortName(std::move(shortname))
|
||||||
|
, description(std::move(description))
|
||||||
|
{
|
||||||
|
}
|
||||||
Emoji() = default;
|
Emoji() = default;
|
||||||
|
|
||||||
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
|
friend QDataStream &operator<<(QDataStream &arch, const Emoji &object)
|
||||||
@@ -33,11 +39,13 @@ struct Emoji {
|
|||||||
|
|
||||||
QString unicode;
|
QString unicode;
|
||||||
QString shortName;
|
QString shortName;
|
||||||
|
QString description;
|
||||||
bool isCustom = false;
|
bool isCustom = false;
|
||||||
|
|
||||||
Q_GADGET
|
Q_GADGET
|
||||||
Q_PROPERTY(QString unicode MEMBER unicode)
|
Q_PROPERTY(QString unicode MEMBER unicode)
|
||||||
Q_PROPERTY(QString shortName MEMBER shortName)
|
Q_PROPERTY(QString shortName MEMBER shortName)
|
||||||
|
Q_PROPERTY(QString description MEMBER description)
|
||||||
Q_PROPERTY(bool isCustom MEMBER isCustom)
|
Q_PROPERTY(bool isCustom MEMBER isCustom)
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -49,6 +57,7 @@ class EmojiModel : public QAbstractListModel
|
|||||||
|
|
||||||
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
Q_PROPERTY(QVariantList history READ history NOTIFY historyChanged)
|
||||||
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
|
Q_PROPERTY(QVariantList categories READ categories CONSTANT)
|
||||||
|
Q_PROPERTY(QVariantList categoriesWithCustom READ categoriesWithCustom CONSTANT)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static EmojiModel &instance()
|
static EmojiModel &instance()
|
||||||
@@ -63,13 +72,16 @@ public:
|
|||||||
InvalidRole = 50,
|
InvalidRole = 50,
|
||||||
DisplayRole = 51,
|
DisplayRole = 51,
|
||||||
ReplacedTextRole = 52,
|
ReplacedTextRole = 52,
|
||||||
|
DescriptionRole = 53,
|
||||||
};
|
};
|
||||||
Q_ENUM(RoleNames);
|
Q_ENUM(RoleNames);
|
||||||
|
|
||||||
enum Category {
|
enum Category {
|
||||||
Custom,
|
Custom,
|
||||||
Search,
|
Search,
|
||||||
|
SearchNoCustom,
|
||||||
History,
|
History,
|
||||||
|
HistoryNoCustom,
|
||||||
Smileys,
|
Smileys,
|
||||||
People,
|
People,
|
||||||
Nature,
|
Nature,
|
||||||
@@ -89,11 +101,14 @@ public:
|
|||||||
|
|
||||||
Q_INVOKABLE QVariantList history() const;
|
Q_INVOKABLE QVariantList history() const;
|
||||||
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
|
Q_INVOKABLE static QVariantList filterModel(const QString &filter, bool limit = true);
|
||||||
|
Q_INVOKABLE static QVariantList filterModelNoCustom(const QString &filter, bool limit = true);
|
||||||
|
|
||||||
Q_INVOKABLE QVariantList emojis(Category category) const;
|
Q_INVOKABLE QVariantList emojis(Category category) const;
|
||||||
|
|
||||||
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
|
Q_INVOKABLE QVariantList tones(const QString &baseEmoji) const;
|
||||||
|
|
||||||
QVariantList categories() const;
|
QVariantList categories() const;
|
||||||
|
QVariantList categoriesWithCustom() const;
|
||||||
|
|
||||||
Q_SIGNALS:
|
Q_SIGNALS:
|
||||||
void historyChanged();
|
void historyChanged();
|
||||||
@@ -103,7 +118,6 @@ public Q_SLOTS:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
static QHash<Category, QVariantList> _emojis;
|
static QHash<Category, QVariantList> _emojis;
|
||||||
static QMultiHash<QString, QVariant> _tones;
|
|
||||||
|
|
||||||
// TODO: Port away from QSettings
|
// TODO: Port away from QSettings
|
||||||
QSettings m_settings;
|
QSettings m_settings;
|
||||||
|
|||||||
3706
src/emojis.h
3706
src/emojis.h
File diff suppressed because it is too large
Load Diff
9
src/emojitones.cpp
Normal file
9
src/emojitones.cpp
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
// SPDX-FileCopyrightText: None
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "emojitones.h"
|
||||||
|
#include "emojimodel.h"
|
||||||
|
|
||||||
|
QMultiHash<QString, QVariant> EmojiTones::_tones = {
|
||||||
|
#include "emojitones_data.h"
|
||||||
|
};
|
||||||
1794
src/emojitones.h
1794
src/emojitones.h
File diff suppressed because it is too large
Load Diff
1784
src/emojitones_data.h
Normal file
1784
src/emojitones_data.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,7 +15,9 @@
|
|||||||
KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent)
|
KeywordNotificationRuleModel::KeywordNotificationRuleModel(QObject *parent)
|
||||||
: QAbstractListModel(parent)
|
: QAbstractListModel(parent)
|
||||||
{
|
{
|
||||||
|
if (Controller::instance().activeConnection()) {
|
||||||
controllerConnectionChanged();
|
controllerConnectionChanged();
|
||||||
|
}
|
||||||
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged);
|
connect(&Controller::instance(), &Controller::activeConnectionChanged, this, &KeywordNotificationRuleModel::controllerConnectionChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,8 +14,6 @@
|
|||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
|
|
||||||
#include <QUrl>
|
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|
||||||
using namespace Quotient;
|
using namespace Quotient;
|
||||||
@@ -183,8 +181,8 @@ void Login::loginWithSso()
|
|||||||
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
|
connectSingleShot(m_connection, &Connection::loginFlowsChanged, this, [this]() {
|
||||||
SsoSession *session = m_connection->prepareForSso(m_deviceName);
|
SsoSession *session = m_connection->prepareForSso(m_deviceName);
|
||||||
m_ssoUrl = session->ssoUrl();
|
m_ssoUrl = session->ssoUrl();
|
||||||
});
|
|
||||||
Q_EMIT ssoUrlChanged();
|
Q_EMIT ssoUrlChanged();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Login::testing() const
|
bool Login::testing() const
|
||||||
|
|||||||
55
src/main.cpp
55
src/main.cpp
@@ -59,6 +59,7 @@
|
|||||||
#include "neochatroom.h"
|
#include "neochatroom.h"
|
||||||
#include "neochatuser.h"
|
#include "neochatuser.h"
|
||||||
#include "notificationsmanager.h"
|
#include "notificationsmanager.h"
|
||||||
|
#include "searchmodel.h"
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
#include "pollhandler.h"
|
#include "pollhandler.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
#include "spacehierarchycache.h"
|
#include "spacehierarchycache.h"
|
||||||
#include "urlhelper.h"
|
#include "urlhelper.h"
|
||||||
#include "userdirectorylistmodel.h"
|
#include "userdirectorylistmodel.h"
|
||||||
|
#include "userfiltermodel.h"
|
||||||
#include "userlistmodel.h"
|
#include "userlistmodel.h"
|
||||||
#include "webshortcutmodel.h"
|
#include "webshortcutmodel.h"
|
||||||
#include "windowcontroller.h"
|
#include "windowcontroller.h"
|
||||||
@@ -219,6 +221,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
qmlRegisterType<MessageEventModel>("org.kde.neochat", 1, 0, "MessageEventModel");
|
||||||
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
|
qmlRegisterType<CollapseStateProxyModel>("org.kde.neochat", 1, 0, "CollapseStateProxyModel");
|
||||||
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
qmlRegisterType<MessageFilterModel>("org.kde.neochat", 1, 0, "MessageFilterModel");
|
||||||
|
qmlRegisterType<UserFilterModel>("org.kde.neochat", 1, 0, "UserFilterModel");
|
||||||
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
qmlRegisterType<PublicRoomListModel>("org.kde.neochat", 1, 0, "PublicRoomListModel");
|
||||||
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
qmlRegisterType<UserDirectoryListModel>("org.kde.neochat", 1, 0, "UserDirectoryListModel");
|
||||||
qmlRegisterType<ServerListModel>("org.kde.neochat", 1, 0, "ServerListModel");
|
qmlRegisterType<ServerListModel>("org.kde.neochat", 1, 0, "ServerListModel");
|
||||||
@@ -228,6 +231,7 @@ int main(int argc, char *argv[])
|
|||||||
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
|
qmlRegisterType<LinkPreviewer>("org.kde.neochat", 1, 0, "LinkPreviewer");
|
||||||
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
qmlRegisterType<CompletionModel>("org.kde.neochat", 1, 0, "CompletionModel");
|
||||||
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
qmlRegisterType<StateModel>("org.kde.neochat", 1, 0, "StateModel");
|
||||||
|
qmlRegisterType<SearchModel>("org.kde.neochat", 1, 0, "SearchModel");
|
||||||
#ifdef QUOTIENT_07
|
#ifdef QUOTIENT_07
|
||||||
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
qmlRegisterType<PollHandler>("org.kde.neochat", 1, 0, "PollHandler");
|
||||||
#endif
|
#endif
|
||||||
@@ -265,6 +269,32 @@ int main(int argc, char *argv[])
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QQmlApplicationEngine engine;
|
QQmlApplicationEngine engine;
|
||||||
|
|
||||||
|
#ifdef HAVE_KDBUSADDONS
|
||||||
|
KDBusService service(KDBusService::Unique);
|
||||||
|
service.connect(&service,
|
||||||
|
&KDBusService::activateRequested,
|
||||||
|
&RoomManager::instance(),
|
||||||
|
[&engine](const QStringList &arguments, const QString &workingDirectory) {
|
||||||
|
Q_UNUSED(workingDirectory);
|
||||||
|
|
||||||
|
QWindow *window = windowFromEngine(&engine);
|
||||||
|
KWindowSystem::updateStartupId(window);
|
||||||
|
|
||||||
|
WindowController::instance().showAndRaiseWindow(QString());
|
||||||
|
|
||||||
|
// Open matrix uri
|
||||||
|
if (arguments.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto args = arguments;
|
||||||
|
args.removeFirst();
|
||||||
|
for (const auto &arg : args) {
|
||||||
|
RoomManager::instance().openResource(arg);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
#endif
|
||||||
|
|
||||||
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
|
||||||
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
|
QObject::connect(&engine, &QQmlApplicationEngine::quit, &app, &QCoreApplication::quit);
|
||||||
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
|
||||||
@@ -294,31 +324,6 @@ int main(int argc, char *argv[])
|
|||||||
QDBusConnection::sessionBus().registerObject("/RoomRunner", &runner, QDBusConnection::ExportScriptableContents);
|
QDBusConnection::sessionBus().registerObject("/RoomRunner", &runner, QDBusConnection::ExportScriptableContents);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef HAVE_KDBUSADDONS
|
|
||||||
KDBusService service(KDBusService::Unique);
|
|
||||||
service.connect(&service,
|
|
||||||
&KDBusService::activateRequested,
|
|
||||||
&RoomManager::instance(),
|
|
||||||
[&engine](const QStringList &arguments, const QString &workingDirectory) {
|
|
||||||
Q_UNUSED(workingDirectory);
|
|
||||||
|
|
||||||
QWindow *window = windowFromEngine(&engine);
|
|
||||||
KWindowSystem::updateStartupId(window);
|
|
||||||
|
|
||||||
WindowController::instance().showAndRaiseWindow(QString());
|
|
||||||
|
|
||||||
// Open matrix uri
|
|
||||||
if (arguments.isEmpty()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
auto args = arguments;
|
|
||||||
args.removeFirst();
|
|
||||||
for (const auto &arg : args) {
|
|
||||||
RoomManager::instance().openResource(arg);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
#endif
|
|
||||||
|
|
||||||
QWindow *window = windowFromEngine(&engine);
|
QWindow *window = windowFromEngine(&engine);
|
||||||
|
|
||||||
WindowController::instance().setWindow(window);
|
WindowController::instance().setWindow(window);
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ Name[ca]=NeoChat
|
|||||||
Name[ca@valencia]=NeoChat
|
Name[ca@valencia]=NeoChat
|
||||||
Name[cs]=NeoChat
|
Name[cs]=NeoChat
|
||||||
Name[de]=NeoChat
|
Name[de]=NeoChat
|
||||||
|
Name[el]=NeoChat
|
||||||
Name[en_GB]=NeoChat
|
Name[en_GB]=NeoChat
|
||||||
Name[es]=NeoChat
|
Name[es]=NeoChat
|
||||||
Name[eu]=NeoChat
|
Name[eu]=NeoChat
|
||||||
@@ -27,6 +28,7 @@ Name[pl]=NeoChat
|
|||||||
Name[pt]=NeoChat
|
Name[pt]=NeoChat
|
||||||
Name[pt_BR]=NeoChat
|
Name[pt_BR]=NeoChat
|
||||||
Name[ro]=NeoChat
|
Name[ro]=NeoChat
|
||||||
|
Name[ru]=NeoChat
|
||||||
Name[sk]=NeoChat
|
Name[sk]=NeoChat
|
||||||
Name[sl]=NeoChat
|
Name[sl]=NeoChat
|
||||||
Name[sv]=NeoChat
|
Name[sv]=NeoChat
|
||||||
@@ -43,6 +45,7 @@ Comment[ca]=Un client per a Matrix, el protocol de comunicacions descentralitzat
|
|||||||
Comment[ca@valencia]=Un client per a Matrix, el protocol de comunicacions descentralitzat
|
Comment[ca@valencia]=Un client per a Matrix, el protocol de comunicacions descentralitzat
|
||||||
Comment[cs]=Klient pro decentralizovaný komunikační protokol matrix
|
Comment[cs]=Klient pro decentralizovaný komunikační protokol matrix
|
||||||
Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll
|
Comment[de]=Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll
|
||||||
|
Comment[el]=Ένας πελάτης για το Matrix, το αποκεντρωμένο πρωτόκολλο επικοινωνίας
|
||||||
Comment[en_GB]=A client for matrix, the decentralised communication protocol
|
Comment[en_GB]=A client for matrix, the decentralised communication protocol
|
||||||
Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
|
Comment[es]=Un cliente para Matrix, el protocolo de comunicaciones descentralizado
|
||||||
Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat
|
Comment[eu]=Matrix, deszentralizatutako komunikazio protokolorako, bezero bat
|
||||||
@@ -63,6 +66,7 @@ Comment[pl]=Program do obsługi matriksa, rozproszonego protokołu porozumiewani
|
|||||||
Comment[pt]=Um cliente para o Matrix, o protocolo descentralizado de comunicações
|
Comment[pt]=Um cliente para o Matrix, o protocolo descentralizado de comunicações
|
||||||
Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado
|
Comment[pt_BR]=Um cliente para o Matrix, o protocolo de comunicação decentralizado
|
||||||
Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată
|
Comment[ro]=Client pentru Matrix, protocolul de comunicare descentralizată
|
||||||
|
Comment[ru]=Клиент для Matrix — децентрализованного коммуникационного протокола
|
||||||
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
|
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
|
||||||
Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix
|
Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix
|
||||||
Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet
|
Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet
|
||||||
@@ -80,6 +84,7 @@ Name[ca]=Missatge nou
|
|||||||
Name[ca@valencia]=Missatge nou
|
Name[ca@valencia]=Missatge nou
|
||||||
Name[cs]=Nová zpráva
|
Name[cs]=Nová zpráva
|
||||||
Name[de]=Neue Nachricht
|
Name[de]=Neue Nachricht
|
||||||
|
Name[el]=Νέο μήνυμα
|
||||||
Name[en_GB]=New message
|
Name[en_GB]=New message
|
||||||
Name[es]=Nuevo mensaje
|
Name[es]=Nuevo mensaje
|
||||||
Name[eu]=Mezu berria
|
Name[eu]=Mezu berria
|
||||||
@@ -100,6 +105,7 @@ Name[pl]=Nowa wiadomość
|
|||||||
Name[pt]=Nova mensagem
|
Name[pt]=Nova mensagem
|
||||||
Name[pt_BR]=Nova mensagem
|
Name[pt_BR]=Nova mensagem
|
||||||
Name[ro]=Mesaj nou
|
Name[ro]=Mesaj nou
|
||||||
|
Name[ru]=Новое сообщение
|
||||||
Name[sk]=Nová správa
|
Name[sk]=Nová správa
|
||||||
Name[sl]=Novo sporočilo
|
Name[sl]=Novo sporočilo
|
||||||
Name[sv]=Nytt meddelande
|
Name[sv]=Nytt meddelande
|
||||||
@@ -113,7 +119,8 @@ Comment[ar]=توجد رسالة جديدة
|
|||||||
Comment[az]=Yeni ismarıc var
|
Comment[az]=Yeni ismarıc var
|
||||||
Comment[ca]=Hi ha un missatge nou
|
Comment[ca]=Hi ha un missatge nou
|
||||||
Comment[ca@valencia]=Hi ha un missatge nou
|
Comment[ca@valencia]=Hi ha un missatge nou
|
||||||
Comment[de]=Es ist eine neue Nachricht vorhanden
|
Comment[de]=Es gibt eine neue Nachricht
|
||||||
|
Comment[el]=Υπάρχει ένα νέο μήνυμα
|
||||||
Comment[en_GB]=There is a new message
|
Comment[en_GB]=There is a new message
|
||||||
Comment[es]=Hay un mensaje nuevo
|
Comment[es]=Hay un mensaje nuevo
|
||||||
Comment[eu]=Mezu berri bat dago
|
Comment[eu]=Mezu berri bat dago
|
||||||
@@ -134,6 +141,7 @@ Comment[pl]=Dostępna jest nowa wiadomość
|
|||||||
Comment[pt]=Tem uma mensagem nova
|
Comment[pt]=Tem uma mensagem nova
|
||||||
Comment[pt_BR]=Existe uma nova mensagem
|
Comment[pt_BR]=Existe uma nova mensagem
|
||||||
Comment[ro]=Este un mesaj nou
|
Comment[ro]=Este un mesaj nou
|
||||||
|
Comment[ru]=Доступно новое сообщение
|
||||||
Comment[sk]=Je nová správa
|
Comment[sk]=Je nová správa
|
||||||
Comment[sl]=Prišlo je novo sporočilo
|
Comment[sl]=Prišlo je novo sporočilo
|
||||||
Comment[sv]=Det finns ett nytt meddelande
|
Comment[sv]=Det finns ett nytt meddelande
|
||||||
@@ -152,6 +160,7 @@ Name[ca]=Invitació nova
|
|||||||
Name[ca@valencia]=Invitació nova
|
Name[ca@valencia]=Invitació nova
|
||||||
Name[cs]=Nová pozvánka
|
Name[cs]=Nová pozvánka
|
||||||
Name[de]=Neue Einladung
|
Name[de]=Neue Einladung
|
||||||
|
Name[el]=Νέα πρόσκληση
|
||||||
Name[en_GB]=New Invitation
|
Name[en_GB]=New Invitation
|
||||||
Name[es]=Nueva invitación
|
Name[es]=Nueva invitación
|
||||||
Name[eu]=Gonbidapen berria
|
Name[eu]=Gonbidapen berria
|
||||||
@@ -168,6 +177,7 @@ Name[pa]=ਨਵਾਂ ਸੱਦਾ
|
|||||||
Name[pl]=Nowe zaproszenie
|
Name[pl]=Nowe zaproszenie
|
||||||
Name[pt]=Novo Convite
|
Name[pt]=Novo Convite
|
||||||
Name[pt_BR]=Novo convite
|
Name[pt_BR]=Novo convite
|
||||||
|
Name[ru]=Новое приглашение
|
||||||
Name[sl]=Novo povabilo
|
Name[sl]=Novo povabilo
|
||||||
Name[sv]=Ny inbjudan
|
Name[sv]=Ny inbjudan
|
||||||
Name[ta]=புதிய அழைப்பிதழ்
|
Name[ta]=புதிய அழைப்பிதழ்
|
||||||
@@ -181,6 +191,7 @@ Comment[ca]=Hi ha una invitació nova a una sala
|
|||||||
Comment[ca@valencia]=Hi ha una invitació nova a una sala
|
Comment[ca@valencia]=Hi ha una invitació nova a una sala
|
||||||
Comment[cs]=Máte novou pozvánku do místnosti
|
Comment[cs]=Máte novou pozvánku do místnosti
|
||||||
Comment[de]=Es gibt eine neue Einladung zu einem Raum
|
Comment[de]=Es gibt eine neue Einladung zu einem Raum
|
||||||
|
Comment[el]=Υπάρχει μια νέα πρόσκληση σε μια αίθουσα
|
||||||
Comment[en_GB]=There is a new invitation to a room
|
Comment[en_GB]=There is a new invitation to a room
|
||||||
Comment[es]=Hay una nueva invitación a una sala
|
Comment[es]=Hay una nueva invitación a una sala
|
||||||
Comment[eu]=Gela baterako gonbidapen berri bat dago
|
Comment[eu]=Gela baterako gonbidapen berri bat dago
|
||||||
@@ -197,6 +208,7 @@ Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
|
|||||||
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
|
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
|
||||||
Comment[pt]=Existe um novo convite para uma sala
|
Comment[pt]=Existe um novo convite para uma sala
|
||||||
Comment[pt_BR]=Existe um novo convite para uma sala
|
Comment[pt_BR]=Existe um novo convite para uma sala
|
||||||
|
Comment[ru]=Доступно новое приглашение в комнату
|
||||||
Comment[sl]=Tam je novo povabilo v sobo
|
Comment[sl]=Tam je novo povabilo v sobo
|
||||||
Comment[sv]=Det finns en ny inbjudan till ett rum
|
Comment[sv]=Det finns en ny inbjudan till ett rum
|
||||||
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
|
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
|
||||||
|
|||||||
@@ -72,7 +72,11 @@
|
|||||||
<default>true</default>
|
<default>true</default>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="CompactLayout" type="bool">
|
<entry name="CompactLayout" type="bool">
|
||||||
<label>Use a compact layout</label>
|
<label>Use a compact chat layout</label>
|
||||||
|
<default>false</default>
|
||||||
|
</entry>
|
||||||
|
<entry name="CompactRoomList" type="bool">
|
||||||
|
<label>Use a compact room list layout</label>
|
||||||
<default>false</default>
|
<default>false</default>
|
||||||
</entry>
|
</entry>
|
||||||
<entry name="ShowRename" type="bool">
|
<entry name="ShowRename" type="bool">
|
||||||
@@ -133,6 +137,14 @@
|
|||||||
<label>The port number of the proxy</label>
|
<label>The port number of the proxy</label>
|
||||||
<default>1080</default>
|
<default>1080</default>
|
||||||
</entry>
|
</entry>
|
||||||
|
<entry name="ProxyUser" type="String">
|
||||||
|
<label>The user of the proxy</label>
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
|
<entry name="ProxyPassword" type="Password">
|
||||||
|
<label>The password of the proxy</label>
|
||||||
|
<default></default>
|
||||||
|
</entry>
|
||||||
</group>
|
</group>
|
||||||
</kcfg>
|
</kcfg>
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,6 @@
|
|||||||
#include <QMediaPlayer>
|
#include <QMediaPlayer>
|
||||||
|
|
||||||
#include <qcoro/qcorosignal.h>
|
#include <qcoro/qcorosignal.h>
|
||||||
#include <qcoro/task.h>
|
|
||||||
|
|
||||||
#include <connection.h>
|
#include <connection.h>
|
||||||
#include <csapi/directory.h>
|
#include <csapi/directory.h>
|
||||||
@@ -32,6 +31,9 @@
|
|||||||
#include <events/roompowerlevelsevent.h>
|
#include <events/roompowerlevelsevent.h>
|
||||||
#include <events/simplestateevents.h>
|
#include <events/simplestateevents.h>
|
||||||
#include <jobs/downloadfilejob.h>
|
#include <jobs/downloadfilejob.h>
|
||||||
|
#ifndef QUOTIENT_07
|
||||||
|
#include <joinstate.h>
|
||||||
|
#endif
|
||||||
#include <qt_connection_util.h>
|
#include <qt_connection_util.h>
|
||||||
|
|
||||||
#include "controller.h"
|
#include "controller.h"
|
||||||
@@ -95,6 +97,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
|||||||
connect(this, &Room::changed, this, [this] {
|
connect(this, &Room::changed, this, [this] {
|
||||||
Q_EMIT canEncryptRoomChanged();
|
Q_EMIT canEncryptRoomChanged();
|
||||||
});
|
});
|
||||||
|
connect(connection, &Connection::capabilitiesLoaded, this, &NeoChatRoom::maxRoomVersionChanged);
|
||||||
}
|
}
|
||||||
|
|
||||||
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
void NeoChatRoom::uploadFile(const QUrl &url, const QString &body)
|
||||||
@@ -109,7 +112,8 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto mime = QMimeDatabase().mimeTypeForUrl(url);
|
auto mime = QMimeDatabase().mimeTypeForUrl(url);
|
||||||
QFileInfo fileInfo(url.toLocalFile());
|
url.setScheme("file");
|
||||||
|
QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||||
EventContent::TypedBase *content;
|
EventContent::TypedBase *content;
|
||||||
if (mime.name().startsWith("image/")) {
|
if (mime.name().startsWith("image/")) {
|
||||||
QImage image(url.toLocalFile());
|
QImage image(url.toLocalFile());
|
||||||
@@ -329,7 +333,7 @@ QString NeoChatRoom::subtitleText()
|
|||||||
{
|
{
|
||||||
static const QRegularExpression blockquote("(\r\n\t|\n|\r\t|)> ");
|
static const QRegularExpression blockquote("(\r\n\t|\n|\r\t|)> ");
|
||||||
static const QRegularExpression heading("(\r\n\t|\n|\r\t|)\\#{1,6} ");
|
static const QRegularExpression heading("(\r\n\t|\n|\r\t|)\\#{1,6} ");
|
||||||
static const QRegularExpression newlines("(\r\n\t|\n|\r\t)");
|
static const QRegularExpression newlines("(\r\n\t|\n|\r\t|\r\n)");
|
||||||
static const QRegularExpression bold1("(\\*\\*|__)(?=\\S)([^\\r]*\\S)\\1");
|
static const QRegularExpression bold1("(\\*\\*|__)(?=\\S)([^\\r]*\\S)\\1");
|
||||||
static const QRegularExpression bold2("(\\*|_)(?=\\S)([^\\r]*\\S)\\1");
|
static const QRegularExpression bold2("(\\*|_)(?=\\S)([^\\r]*\\S)\\1");
|
||||||
static const QRegularExpression strike1("~~(.*)~~");
|
static const QRegularExpression strike1("~~(.*)~~");
|
||||||
@@ -600,8 +604,10 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
|||||||
} else {
|
} else {
|
||||||
return i18n("self-banned from the room");
|
return i18n("self-banned from the room");
|
||||||
}
|
}
|
||||||
case MembershipType::Knock:
|
case MembershipType::Knock: {
|
||||||
return i18n("requested an invite");
|
QString reason(e.contentJson()["reason"_ls].toString().toHtmlEscaped());
|
||||||
|
return reason.isEmpty() ? i18n("requested an invite") : i18n("requested an invite with reason: %1", reason);
|
||||||
|
}
|
||||||
default:;
|
default:;
|
||||||
}
|
}
|
||||||
return i18n("made something unknown");
|
return i18n("made something unknown");
|
||||||
@@ -940,6 +946,305 @@ void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
|
|||||||
// Not emitting historyVisibilityChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
// Not emitting historyVisibilityChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel)
|
||||||
|
{
|
||||||
|
if (joinedCount() <= 1) {
|
||||||
|
qWarning() << "Cannot modify the power level of the only user";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!canSendState("m.room.power_levels")) {
|
||||||
|
qWarning() << "Power level too low to set user power levels";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
if (!isMember(userID)) {
|
||||||
|
#else
|
||||||
|
if (memberJoinState(user(userID)) == JoinState::Join) {
|
||||||
|
#endif
|
||||||
|
qWarning() << "User is not a member of this room so power level cannot be set";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int clampPowerLevel = std::clamp(powerLevel, 0, 100);
|
||||||
|
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
|
||||||
|
#else
|
||||||
|
auto powerLevelContent = getCurrentState<RoomPowerLevelsEvent>()->contentJson();
|
||||||
|
#endif
|
||||||
|
auto powerLevelUserOverrides = powerLevelContent["users"].toObject();
|
||||||
|
|
||||||
|
if (powerLevelUserOverrides[userID] != clampPowerLevel) {
|
||||||
|
powerLevelUserOverrides[userID] = clampPowerLevel;
|
||||||
|
powerLevelContent["users"] = powerLevelUserOverrides;
|
||||||
|
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
setState("m.room.power_levels", "", powerLevelContent);
|
||||||
|
#else
|
||||||
|
setState<RoomPowerLevelsEvent>(QJsonObject{{"type", "m.room.power_levels"}, {"state_key", ""}, {"content", powerLevelContent}});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const
|
||||||
|
{
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
const auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
|
||||||
|
#else
|
||||||
|
const auto powerLevelEvent = getCurrentState<RoomPowerLevelsEvent>();
|
||||||
|
#endif
|
||||||
|
if (eventName == "ban") {
|
||||||
|
return powerLevelEvent->ban();
|
||||||
|
} else if (eventName == "kick") {
|
||||||
|
return powerLevelEvent->kick();
|
||||||
|
} else if (eventName == "invite") {
|
||||||
|
return powerLevelEvent->invite();
|
||||||
|
} else if (eventName == "redact") {
|
||||||
|
return powerLevelEvent->redact();
|
||||||
|
} else if (eventName == "users_default") {
|
||||||
|
return powerLevelEvent->usersDefault();
|
||||||
|
} else if (eventName == "state_default") {
|
||||||
|
return powerLevelEvent->stateDefault();
|
||||||
|
} else if (eventName == "events_default") {
|
||||||
|
return powerLevelEvent->eventsDefault();
|
||||||
|
} else if (isStateEvent) {
|
||||||
|
return powerLevelEvent->powerLevelForState(eventName);
|
||||||
|
} else {
|
||||||
|
return powerLevelEvent->powerLevelForEvent(eventName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent)
|
||||||
|
{
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
|
||||||
|
#else
|
||||||
|
auto powerLevelContent = getCurrentState<RoomPowerLevelsEvent>()->contentJson();
|
||||||
|
#endif
|
||||||
|
int clampPowerLevel = std::clamp(newPowerLevel, 0, 100);
|
||||||
|
int powerLevel = 0;
|
||||||
|
|
||||||
|
if (powerLevelContent.contains(eventName)) {
|
||||||
|
powerLevel = powerLevelContent[eventName].toInt();
|
||||||
|
|
||||||
|
if (powerLevel != clampPowerLevel) {
|
||||||
|
powerLevelContent[eventName] = clampPowerLevel;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
auto eventPowerLevels = powerLevelContent["events"].toObject();
|
||||||
|
|
||||||
|
if (eventPowerLevels.contains(eventName)) {
|
||||||
|
powerLevel = eventPowerLevels[eventName].toInt();
|
||||||
|
} else {
|
||||||
|
if (isStateEvent) {
|
||||||
|
powerLevel = powerLevelContent["state_default"].toInt();
|
||||||
|
} else {
|
||||||
|
powerLevel = powerLevelContent["events_default"].toInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (powerLevel != clampPowerLevel) {
|
||||||
|
eventPowerLevels[eventName] = clampPowerLevel;
|
||||||
|
powerLevelContent["events"] = eventPowerLevels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifdef QUOTIENT_07
|
||||||
|
setState("m.room.power_levels", "", powerLevelContent);
|
||||||
|
#else
|
||||||
|
setState<RoomPowerLevelsEvent>(QJsonObject{{"type", "m.room.power_levels"}, {"state_key", ""}, {"content", powerLevelContent}});
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::defaultUserPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("users_default");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setDefaultUserPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("users_default", newPowerLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::invitePowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("invite");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setInvitePowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("invite", newPowerLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::kickPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("kick");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setKickPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("kick", newPowerLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::banPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("ban");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setBanPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("ban", newPowerLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::redactPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("redact");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setRedactPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("redact", newPowerLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::statePowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("state_default");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setStatePowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("state_default", newPowerLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::defaultEventPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("events_default");
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setDefaultEventPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("events_default", newPowerLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::powerLevelPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.power_levels", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setPowerLevelPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.power_levels", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::namePowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.name", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setNamePowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.name", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::avatarPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.avatar", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setAvatarPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.avatar", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::canonicalAliasPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.canonical_alias", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setCanonicalAliasPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.canonical_alias", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::topicPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.topic", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setTopicPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.topic", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::encryptionPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.encryption", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setEncryptionPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.encryption", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::historyVisibilityPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.history_visibility", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setHistoryVisibilityPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.history_visibility", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::pinnedEventsPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.pinned_events", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setPinnedEventsPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.pinned_events", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::tombstonePowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.tombstone", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setTombstonePowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.tombstone", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::serverAclPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.room.server_acl", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setServerAclPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.room.server_acl", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::spaceChildPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.space.child", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setSpaceChildPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.space.child", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::spaceParentPowerLevel() const
|
||||||
|
{
|
||||||
|
return powerLevel("m.space.parent", true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void NeoChatRoom::setSpaceParentPowerLevel(const int &newPowerLevel)
|
||||||
|
{
|
||||||
|
setPowerLevel("m.space.parent", newPowerLevel, true);
|
||||||
|
}
|
||||||
|
|
||||||
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
|
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
|
||||||
{
|
{
|
||||||
QStringList events;
|
QStringList events;
|
||||||
@@ -1368,3 +1673,14 @@ void NeoChatRoom::setCanonicalAlias(const QString &newAlias)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int NeoChatRoom::maxRoomVersion() const
|
||||||
|
{
|
||||||
|
int maxVersion = 0;
|
||||||
|
for (auto roomVersion : connection()->availableRoomVersions()) {
|
||||||
|
if (roomVersion.id.toInt() > maxVersion) {
|
||||||
|
maxVersion = roomVersion.id.toInt();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxVersion;
|
||||||
|
}
|
||||||
|
|||||||
@@ -47,9 +47,32 @@ class NeoChatRoom : public Quotient::Room
|
|||||||
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
Q_PROPERTY(QString avatarMediaId READ avatarMediaId NOTIFY avatarChanged STORED false)
|
||||||
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
Q_PROPERTY(bool readMarkerLoaded READ readMarkerLoaded NOTIFY readMarkerLoadedChanged)
|
||||||
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
Q_PROPERTY(QDateTime lastActiveTime READ lastActiveTime NOTIFY lastActiveTimeChanged)
|
||||||
|
Q_PROPERTY(bool isSpace READ isSpace CONSTANT)
|
||||||
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
Q_PROPERTY(bool isInvite READ isInvite NOTIFY isInviteChanged)
|
||||||
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
Q_PROPERTY(QString joinRule READ joinRule WRITE setJoinRule NOTIFY joinRuleChanged)
|
||||||
Q_PROPERTY(QString historyVisibility READ historyVisibility WRITE setHistoryVisibility NOTIFY historyVisibilityChanged)
|
Q_PROPERTY(QString historyVisibility READ historyVisibility WRITE setHistoryVisibility NOTIFY historyVisibilityChanged)
|
||||||
|
|
||||||
|
// Properties for the various permission levels for the room
|
||||||
|
Q_PROPERTY(int defaultUserPowerLevel READ defaultUserPowerLevel WRITE setDefaultUserPowerLevel NOTIFY defaultUserPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int invitePowerLevel READ invitePowerLevel WRITE setInvitePowerLevel NOTIFY invitePowerLevelChanged)
|
||||||
|
Q_PROPERTY(int kickPowerLevel READ kickPowerLevel WRITE setKickPowerLevel NOTIFY kickPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int banPowerLevel READ banPowerLevel WRITE setBanPowerLevel NOTIFY banPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int redactPowerLevel READ redactPowerLevel WRITE setRedactPowerLevel NOTIFY redactPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int statePowerLevel READ statePowerLevel WRITE setStatePowerLevel NOTIFY statePowerLevelChanged)
|
||||||
|
Q_PROPERTY(int defaultEventPowerLevel READ defaultEventPowerLevel WRITE setDefaultEventPowerLevel NOTIFY defaultEventPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int powerLevelPowerLevel READ powerLevelPowerLevel WRITE setPowerLevelPowerLevel NOTIFY powerLevelPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int namePowerLevel READ namePowerLevel WRITE setNamePowerLevel NOTIFY namePowerLevelChanged)
|
||||||
|
Q_PROPERTY(int avatarPowerLevel READ avatarPowerLevel WRITE setAvatarPowerLevel NOTIFY avatarPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int canonicalAliasPowerLevel READ canonicalAliasPowerLevel WRITE setCanonicalAliasPowerLevel NOTIFY canonicalAliasPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int topicPowerLevel READ topicPowerLevel WRITE setTopicPowerLevel NOTIFY topicPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int encryptionPowerLevel READ encryptionPowerLevel WRITE setEncryptionPowerLevel NOTIFY encryptionPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int historyVisibilityPowerLevel READ historyVisibilityPowerLevel WRITE setHistoryVisibilityPowerLevel NOTIFY historyVisibilityPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int pinnedEventsPowerLevel READ pinnedEventsPowerLevel WRITE setPinnedEventsPowerLevel NOTIFY pinnedEventsPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int tombstonePowerLevel READ tombstonePowerLevel WRITE setTombstonePowerLevel NOTIFY tombstonePowerLevelChanged)
|
||||||
|
Q_PROPERTY(int serverAclPowerLevel READ serverAclPowerLevel WRITE setServerAclPowerLevel NOTIFY serverAclPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int spaceChildPowerLevel READ spaceChildPowerLevel WRITE setSpaceChildPowerLevel NOTIFY spaceChildPowerLevelChanged)
|
||||||
|
Q_PROPERTY(int spaceParentPowerLevel READ spaceParentPowerLevel WRITE setSpaceParentPowerLevel NOTIFY spaceParentPowerLevelChanged)
|
||||||
|
|
||||||
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
Q_PROPERTY(QString htmlSafeDisplayName READ htmlSafeDisplayName NOTIFY displayNameChanged)
|
||||||
Q_PROPERTY(PushNotificationState::State pushNotificationState MEMBER m_currentPushNotificationState WRITE setPushNotificationState NOTIFY
|
Q_PROPERTY(PushNotificationState::State pushNotificationState MEMBER m_currentPushNotificationState WRITE setPushNotificationState NOTIFY
|
||||||
pushNotificationStateChanged)
|
pushNotificationStateChanged)
|
||||||
@@ -65,6 +88,13 @@ class NeoChatRoom : public Quotient::Room
|
|||||||
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
|
Q_PROPERTY(QString chatBoxAttachmentPath READ chatBoxAttachmentPath WRITE setChatBoxAttachmentPath NOTIFY chatBoxAttachmentPathChanged)
|
||||||
Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged)
|
Q_PROPERTY(bool canEncryptRoom READ canEncryptRoom NOTIFY canEncryptRoomChanged)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Get the maximum room version that the server supports.
|
||||||
|
*
|
||||||
|
* Only returns main integer room versions (i.e. no msc room versions).
|
||||||
|
*/
|
||||||
|
Q_PROPERTY(int maxRoomVersion READ maxRoomVersion NOTIFY maxRoomVersionChanged)
|
||||||
|
|
||||||
public:
|
public:
|
||||||
enum MessageType {
|
enum MessageType {
|
||||||
Positive,
|
Positive,
|
||||||
@@ -117,6 +147,68 @@ public:
|
|||||||
[[nodiscard]] QString historyVisibility() const;
|
[[nodiscard]] QString historyVisibility() const;
|
||||||
void setHistoryVisibility(const QString &historyVisibilityRule);
|
void setHistoryVisibility(const QString &historyVisibilityRule);
|
||||||
|
|
||||||
|
Q_INVOKABLE void setUserPowerLevel(const QString &userID, const int &powerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int powerLevel(const QString &eventName, const bool &isStateEvent = false) const;
|
||||||
|
void setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent = false);
|
||||||
|
|
||||||
|
[[nodiscard]] int defaultUserPowerLevel() const;
|
||||||
|
void setDefaultUserPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int invitePowerLevel() const;
|
||||||
|
void setInvitePowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int kickPowerLevel() const;
|
||||||
|
void setKickPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int banPowerLevel() const;
|
||||||
|
void setBanPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int redactPowerLevel() const;
|
||||||
|
void setRedactPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int statePowerLevel() const;
|
||||||
|
void setStatePowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int defaultEventPowerLevel() const;
|
||||||
|
void setDefaultEventPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int powerLevelPowerLevel() const;
|
||||||
|
void setPowerLevelPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int namePowerLevel() const;
|
||||||
|
void setNamePowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int avatarPowerLevel() const;
|
||||||
|
void setAvatarPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int canonicalAliasPowerLevel() const;
|
||||||
|
void setCanonicalAliasPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int topicPowerLevel() const;
|
||||||
|
void setTopicPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int encryptionPowerLevel() const;
|
||||||
|
void setEncryptionPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int historyVisibilityPowerLevel() const;
|
||||||
|
void setHistoryVisibilityPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int pinnedEventsPowerLevel() const;
|
||||||
|
void setPinnedEventsPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int tombstonePowerLevel() const;
|
||||||
|
void setTombstonePowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int serverAclPowerLevel() const;
|
||||||
|
void setServerAclPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int spaceChildPowerLevel() const;
|
||||||
|
void setSpaceChildPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
|
[[nodiscard]] int spaceParentPowerLevel() const;
|
||||||
|
void setSpaceParentPowerLevel(const int &newPowerLevel);
|
||||||
|
|
||||||
[[nodiscard]] bool hasFileUploading() const
|
[[nodiscard]] bool hasFileUploading() const
|
||||||
{
|
{
|
||||||
return m_hasFileUploading;
|
return m_hasFileUploading;
|
||||||
@@ -223,6 +315,8 @@ public:
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
int maxRoomVersion() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QSet<const Quotient::RoomEvent *> highlights;
|
QSet<const Quotient::RoomEvent *> highlights;
|
||||||
|
|
||||||
@@ -274,6 +368,26 @@ Q_SIGNALS:
|
|||||||
void canEncryptRoomChanged();
|
void canEncryptRoomChanged();
|
||||||
void joinRuleChanged();
|
void joinRuleChanged();
|
||||||
void historyVisibilityChanged();
|
void historyVisibilityChanged();
|
||||||
|
void maxRoomVersionChanged();
|
||||||
|
void defaultUserPowerLevelChanged();
|
||||||
|
void invitePowerLevelChanged();
|
||||||
|
void kickPowerLevelChanged();
|
||||||
|
void banPowerLevelChanged();
|
||||||
|
void redactPowerLevelChanged();
|
||||||
|
void statePowerLevelChanged();
|
||||||
|
void defaultEventPowerLevelChanged();
|
||||||
|
void powerLevelPowerLevelChanged();
|
||||||
|
void namePowerLevelChanged();
|
||||||
|
void avatarPowerLevelChanged();
|
||||||
|
void canonicalAliasPowerLevelChanged();
|
||||||
|
void topicPowerLevelChanged();
|
||||||
|
void encryptionPowerLevelChanged();
|
||||||
|
void historyVisibilityPowerLevelChanged();
|
||||||
|
void pinnedEventsPowerLevelChanged();
|
||||||
|
void tombstonePowerLevelChanged();
|
||||||
|
void serverAclPowerLevelChanged();
|
||||||
|
void spaceChildPowerLevelChanged();
|
||||||
|
void spaceParentPowerLevelChanged();
|
||||||
|
|
||||||
public Q_SLOTS:
|
public Q_SLOTS:
|
||||||
void uploadFile(const QUrl &url, const QString &body = QString());
|
void uploadFile(const QUrl &url, const QString &body = QString());
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
#include <QImage>
|
|
||||||
#include <QJsonArray>
|
#include <QJsonArray>
|
||||||
|
|
||||||
#include <KLocalizedString>
|
#include <KLocalizedString>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ Name[ca]=NeoChat
|
|||||||
Name[ca@valencia]=NeoChat
|
Name[ca@valencia]=NeoChat
|
||||||
Name[cs]=NeoChat
|
Name[cs]=NeoChat
|
||||||
Name[de]=NeoChat
|
Name[de]=NeoChat
|
||||||
|
Name[el]=NeoChat
|
||||||
Name[en_GB]=NeoChat
|
Name[en_GB]=NeoChat
|
||||||
Name[es]=NeoChat
|
Name[es]=NeoChat
|
||||||
Name[eu]=NeoChat
|
Name[eu]=NeoChat
|
||||||
@@ -28,6 +29,7 @@ Name[pl]=NeoChat
|
|||||||
Name[pt]=NeoChat
|
Name[pt]=NeoChat
|
||||||
Name[pt_BR]=NeoChat
|
Name[pt_BR]=NeoChat
|
||||||
Name[ro]=NeoChat
|
Name[ro]=NeoChat
|
||||||
|
Name[ru]=NeoChat
|
||||||
Name[sk]=NeoChat
|
Name[sk]=NeoChat
|
||||||
Name[sl]=NeoChat
|
Name[sl]=NeoChat
|
||||||
Name[sv]=NeoChat
|
Name[sv]=NeoChat
|
||||||
@@ -41,6 +43,8 @@ Comment[ar]=اعثر على غرف في نيوتشات
|
|||||||
Comment[az]=NeoChat-da otaqları tapın
|
Comment[az]=NeoChat-da otaqları tapın
|
||||||
Comment[ca]=Cerca sales en el NeoChat
|
Comment[ca]=Cerca sales en el NeoChat
|
||||||
Comment[ca@valencia]=Busca sales en NeoChat
|
Comment[ca@valencia]=Busca sales en NeoChat
|
||||||
|
Comment[de]=Räume in NeoChat finden
|
||||||
|
Comment[el]=Εύρεση αιθουσών στο NeoChat
|
||||||
Comment[en_GB]=Find rooms in NeoChat
|
Comment[en_GB]=Find rooms in NeoChat
|
||||||
Comment[es]=Buscar salas en NeoChat
|
Comment[es]=Buscar salas en NeoChat
|
||||||
Comment[eu]=Bilatu gelak NeoChat-en
|
Comment[eu]=Bilatu gelak NeoChat-en
|
||||||
@@ -56,6 +60,7 @@ Comment[nl]=Rooms zoeken in NeoChat
|
|||||||
Comment[pl]=Znajdź pokoje w NeoChat
|
Comment[pl]=Znajdź pokoje w NeoChat
|
||||||
Comment[pt]=Procurar salas no NeoChat
|
Comment[pt]=Procurar salas no NeoChat
|
||||||
Comment[pt_BR]=Encontrar salas no NeoChat
|
Comment[pt_BR]=Encontrar salas no NeoChat
|
||||||
|
Comment[ru]=Поиск комнаты NeoChat
|
||||||
Comment[sl]=Najdi sobe v NeoChatu
|
Comment[sl]=Najdi sobe v NeoChatu
|
||||||
Comment[sv]=Sök efter rum i NeoChat
|
Comment[sv]=Sök efter rum i NeoChat
|
||||||
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
|
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
|
||||||
|
|||||||
@@ -14,10 +14,8 @@ QQC2.ToolBar {
|
|||||||
id: chatBar
|
id: chatBar
|
||||||
property alias inputFieldText: inputField.text
|
property alias inputFieldText: inputField.text
|
||||||
property alias textField: inputField
|
property alias textField: inputField
|
||||||
property alias emojiPaneOpened: emojiButton.checked
|
|
||||||
property alias cursorPosition: inputField.cursorPosition
|
property alias cursorPosition: inputField.cursorPosition
|
||||||
|
|
||||||
signal closeAllTriggered()
|
|
||||||
signal inputFieldForceActiveFocusTriggered()
|
signal inputFieldForceActiveFocusTriggered()
|
||||||
signal messageSent()
|
signal messageSent()
|
||||||
|
|
||||||
@@ -87,6 +85,7 @@ QQC2.ToolBar {
|
|||||||
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
Kirigami.Theme.inherit: false
|
Kirigami.Theme.inherit: false
|
||||||
|
Kirigami.SpellChecking.enabled: true
|
||||||
|
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
selectionColor: Kirigami.Theme.highlightColor
|
selectionColor: Kirigami.Theme.highlightColor
|
||||||
@@ -205,6 +204,14 @@ QQC2.ToolBar {
|
|||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
checkable: true
|
checkable: true
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (emojiDialog.visible) {
|
||||||
|
emojiDialog.close()
|
||||||
|
} else {
|
||||||
|
emojiDialog.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.ToolTip.text: text
|
QQC2.ToolTip.text: text
|
||||||
QQC2.ToolTip.visible: hovered
|
QQC2.ToolTip.visible: hovered
|
||||||
}
|
}
|
||||||
@@ -224,6 +231,19 @@ QQC2.ToolBar {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EmojiDialog {
|
||||||
|
id: emojiDialog
|
||||||
|
x: parent.width - implicitWidth
|
||||||
|
y: -implicitHeight - Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
modal: false
|
||||||
|
includeCustom: true
|
||||||
|
closeOnChosen: false
|
||||||
|
|
||||||
|
onChosen: insertText(emoji)
|
||||||
|
onClosed: if (emojiButton.checked) emojiButton.checked = false
|
||||||
|
}
|
||||||
|
|
||||||
CompletionMenu {
|
CompletionMenu {
|
||||||
id: completionMenu
|
id: completionMenu
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
|
|||||||
@@ -46,32 +46,6 @@ ColumnLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.Separator {
|
|
||||||
id: emojiPickerLoaderSeparator
|
|
||||||
visible: emojiPickerLoader.visible
|
|
||||||
Layout.fillWidth: true
|
|
||||||
height: visible ? implicitHeight : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
Loader {
|
|
||||||
id: emojiPickerLoader
|
|
||||||
active: visible
|
|
||||||
visible: chatBar.emojiPaneOpened
|
|
||||||
Layout.fillWidth: true
|
|
||||||
sourceComponent: QQC2.Pane {
|
|
||||||
topPadding: 0
|
|
||||||
bottomPadding: 0
|
|
||||||
rightPadding: 0
|
|
||||||
leftPadding: 0
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
contentItem: EmojiPicker {
|
|
||||||
textArea: chatBar.textField
|
|
||||||
onChosen: insertText(emoji)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
Kirigami.Separator {
|
Kirigami.Separator {
|
||||||
id: replySeparator
|
id: replySeparator
|
||||||
visible: replyPane.visible
|
visible: replyPane.visible
|
||||||
@@ -113,9 +87,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
onCloseAllTriggered: closeAll()
|
|
||||||
onMessageSent: {
|
onMessageSent: {
|
||||||
closeAll()
|
|
||||||
chatBox.messageSent();
|
chatBox.messageSent();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,9 +109,4 @@ ColumnLayout {
|
|||||||
function focusInputField() {
|
function focusInputField() {
|
||||||
chatBar.inputFieldForceActiveFocusTriggered()
|
chatBar.inputFieldForceActiveFocusTriggered()
|
||||||
}
|
}
|
||||||
|
|
||||||
function closeAll() {
|
|
||||||
// TODO clear();
|
|
||||||
chatBar.emojiPaneOpened = false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
59
src/qml/Component/Emoji/EmojiDelegate.qml
Normal file
59
src/qml/Component/Emoji/EmojiDelegate.qml
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
QQC2.ItemDelegate {
|
||||||
|
id: emojiDelegate
|
||||||
|
|
||||||
|
property string name
|
||||||
|
property string emoji
|
||||||
|
property bool showTones: false
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: emojiDelegate.name
|
||||||
|
QQC2.ToolTip.visible: hovered && emojiDelegate.name !== ""
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
leftInset: Kirigami.Units.smallSpacing
|
||||||
|
topInset: Kirigami.Units.smallSpacing
|
||||||
|
rightInset: Kirigami.Units.smallSpacing
|
||||||
|
bottomInset: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
contentItem: Item {
|
||||||
|
Kirigami.Heading {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: !emojiDelegate.emoji.startsWith("image")
|
||||||
|
text: emojiDelegate.emoji
|
||||||
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
verticalAlignment: Text.AlignVCenter
|
||||||
|
font.family: "emoji"
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
width: Kirigami.Units.gridUnit * 0.5
|
||||||
|
height: Kirigami.Units.gridUnit * 0.5
|
||||||
|
source: "arrow-down"
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
visible: emojiDelegate.showTones
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
visible: emojiDelegate.emoji.startsWith("image")
|
||||||
|
source: visible ? emojiDelegate.emoji : ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Rectangle {
|
||||||
|
color: emojiDelegate.checked ? Kirigami.Theme.highlightColor : Kirigami.Theme.backgroundColor
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
anchors.fill: parent
|
||||||
|
color: Kirigami.Theme.highlightColor
|
||||||
|
opacity: emojiDelegate.hovered && !emojiDelegate.pressed ? 0.2 : 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
87
src/qml/Component/Emoji/EmojiGrid.qml
Normal file
87
src/qml/Component/Emoji/EmojiGrid.qml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
QQC2.ScrollView {
|
||||||
|
id: emojiGrid
|
||||||
|
|
||||||
|
property alias model: emojis.model
|
||||||
|
property alias count: emojis.count
|
||||||
|
required property int targetIconSize
|
||||||
|
readonly property int emojisPerRow: emojis.width / targetIconSize
|
||||||
|
required property bool withCustom
|
||||||
|
readonly property var searchCategory: withCustom ? EmojiModel.Search : EmojiModel.SearchNoCustom
|
||||||
|
required property QtObject header
|
||||||
|
|
||||||
|
signal chosen(string unicode)
|
||||||
|
|
||||||
|
onActiveFocusChanged: if (activeFocus) {
|
||||||
|
emojis.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
GridView {
|
||||||
|
id: emojis
|
||||||
|
|
||||||
|
anchors.fill: parent
|
||||||
|
anchors.rightMargin: parent.QQC2.ScrollBar.vertical.visible ? parent.QQC2.ScrollBar.vertical.width : 0
|
||||||
|
|
||||||
|
currentIndex: -1
|
||||||
|
keyNavigationEnabled: true
|
||||||
|
onActiveFocusChanged: if (activeFocus && currentIndex === -1) {
|
||||||
|
currentIndex = 0
|
||||||
|
} else {
|
||||||
|
currentIndex = -1
|
||||||
|
}
|
||||||
|
onModelChanged: currentIndex = -1
|
||||||
|
|
||||||
|
cellWidth: emojis.width / emojiGrid.emojisPerRow
|
||||||
|
cellHeight: emojiGrid.targetIconSize
|
||||||
|
|
||||||
|
KeyNavigation.up: emojiGrid.header
|
||||||
|
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
delegate: EmojiDelegate {
|
||||||
|
id: emojiDelegate
|
||||||
|
checked: emojis.currentIndex === model.index
|
||||||
|
emoji: modelData.unicode
|
||||||
|
name: modelData.shortName
|
||||||
|
|
||||||
|
width: emojis.cellWidth
|
||||||
|
height: emojis.cellHeight
|
||||||
|
|
||||||
|
Keys.onEnterPressed: clicked()
|
||||||
|
Keys.onReturnPressed: clicked()
|
||||||
|
onClicked: {
|
||||||
|
emojiGrid.chosen(modelData.isCustom ? modelData.shortName : modelData.unicode)
|
||||||
|
EmojiModel.emojiUsed(modelData)
|
||||||
|
}
|
||||||
|
Keys.onSpacePressed: pressAndHold()
|
||||||
|
onPressAndHold: {
|
||||||
|
if (EmojiModel.tones(modelData.shortName).length === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let tones = tonesPopupComponent.createObject(emojiDelegate, {shortName: modelData.shortName, unicode: modelData.unicode, categoryIconSize: emojiGrid.targetIconSize})
|
||||||
|
tones.open()
|
||||||
|
tones.forceActiveFocus()
|
||||||
|
}
|
||||||
|
showTones: EmojiModel.tones(modelData.shortName).length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
text: i18n("No emojis")
|
||||||
|
visible: emojis.count === 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: tonesPopupComponent
|
||||||
|
EmojiTonesPicker {
|
||||||
|
onChosen: emojiGrid.chosen(emoji)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,63 +1,74 @@
|
|||||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
// SPDX-License-Identifier: GPL-3.0-only
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.15
|
import QtQuick 2.15
|
||||||
import QtQuick.Controls 2.15 as QQC2
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
import QtQuick.Layouts 1.15
|
import QtQuick.Layouts 1.15
|
||||||
import org.kde.kirigami 2.15 as Kirigami
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
|
||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
id: _picker
|
id: root
|
||||||
|
|
||||||
property var emojiCategory: EmojiModel.History
|
property bool includeCustom: false
|
||||||
property var textArea
|
property bool showQuickReaction: false
|
||||||
readonly property var emojiModel: EmojiModel
|
|
||||||
|
readonly property var currentEmojiModel: {
|
||||||
|
if (includeCustom) {
|
||||||
|
EmojiModel.categoriesWithCustom
|
||||||
|
} else {
|
||||||
|
EmojiModel.categories
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
readonly property int categoryIconSize: Math.round(Kirigami.Units.gridUnit * 2.5)
|
||||||
|
readonly property var currentCategory: currentEmojiModel[categories.currentIndex].category
|
||||||
|
readonly property alias categoryCount: categories.count
|
||||||
|
|
||||||
signal chosen(string emoji)
|
signal chosen(string emoji)
|
||||||
|
|
||||||
|
onActiveFocusChanged: if (activeFocus) {
|
||||||
|
searchField.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
QQC2.ScrollView {
|
QQC2.ScrollView {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 2 + QQC2.ScrollBar.horizontal.height + 2 // for the focus line
|
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
|
||||||
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
||||||
|
|
||||||
ListView {
|
ListView {
|
||||||
|
id: categories
|
||||||
clip: true
|
clip: true
|
||||||
|
focus: true
|
||||||
orientation: ListView.Horizontal
|
orientation: ListView.Horizontal
|
||||||
|
|
||||||
model: EmojiModel.categories
|
Keys.onReturnPressed: if (emojiGrid.count > 0) emojiGrid.focus = true
|
||||||
delegate: QQC2.ItemDelegate {
|
Keys.onEnterPressed: if (emojiGrid.count > 0) emojiGrid.focus = true
|
||||||
id: del
|
KeyNavigation.down: emojiGrid.count > 0 ? emojiGrid : categories
|
||||||
|
KeyNavigation.tab: emojiGrid.count > 0 ? emojiGrid : categories
|
||||||
|
|
||||||
width: contentItem.Layout.preferredWidth
|
keyNavigationEnabled: true
|
||||||
height: Kirigami.Units.gridUnit * 2
|
keyNavigationWraps: true
|
||||||
|
Keys.forwardTo: searchField
|
||||||
|
interactive: width !== contentWidth
|
||||||
|
|
||||||
contentItem: Kirigami.Heading {
|
model: root.currentEmojiModel
|
||||||
horizontalAlignment: Text.AlignHCenter
|
Component.onCompleted: categories.forceActiveFocus()
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
level: modelData.category === EmojiModel.Custom ? 4 : 1
|
|
||||||
|
|
||||||
Layout.preferredWidth: modelData.category === EmojiModel.Custom ? implicitWidth + Kirigami.Units.largeSpacing : Kirigami.Units.gridUnit * 2
|
delegate: EmojiDelegate {
|
||||||
|
width: root.categoryIconSize
|
||||||
|
height: width
|
||||||
|
|
||||||
font.family: modelData.category === EmojiModel.Custom ? Kirigami.Theme.defaultFont.family : 'emoji'
|
checked: categories.currentIndex === model.index
|
||||||
text: modelData.category === EmojiModel.Custom ? i18n("Custom") : modelData.emoji
|
emoji: modelData.emoji
|
||||||
|
name: modelData.name
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
categories.currentIndex = index
|
||||||
|
categories.focus = true
|
||||||
}
|
}
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.bottom: parent.bottom
|
|
||||||
|
|
||||||
width: parent.width
|
|
||||||
height: 2
|
|
||||||
|
|
||||||
visible: _picker.emojiCategory === modelData.category
|
|
||||||
|
|
||||||
color: Kirigami.Theme.focusColor
|
|
||||||
}
|
|
||||||
|
|
||||||
onClicked: _picker.emojiCategory = modelData.category
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,57 +78,58 @@ ColumnLayout {
|
|||||||
Layout.preferredHeight: 1
|
Layout.preferredHeight: 1
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ScrollView {
|
Kirigami.SearchField {
|
||||||
|
id: searchField
|
||||||
|
Layout.margins: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The focus is manged by the parent and we don't want to use the standard
|
||||||
|
* shortcut as it could block other SearchFields from using it.
|
||||||
|
*/
|
||||||
|
focusSequence: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
EmojiGrid {
|
||||||
|
id: emojiGrid
|
||||||
|
targetIconSize: root.currentCategory === EmojiModel.Custom ? Kirigami.Units.gridUnit * 3 : root.categoryIconSize // Custom emojis are bigger
|
||||||
|
model: searchField.text.length === 0 ? EmojiModel.emojis(root.currentCategory) : (root.includeCustom ? EmojiModel.filterModel(searchField.text, false) : EmojiModel.filterModelNoCustom(searchField.text, false))
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 8
|
|
||||||
Layout.fillHeight: true
|
Layout.fillHeight: true
|
||||||
|
withCustom: root.includeCustom
|
||||||
GridView {
|
onChosen: root.chosen(unicode)
|
||||||
cellWidth: Kirigami.Units.gridUnit * 2
|
header: categories
|
||||||
cellHeight: Kirigami.Units.gridUnit * 2
|
Keys.forwardTo: searchField
|
||||||
|
|
||||||
clip: true
|
|
||||||
|
|
||||||
model: _picker.emojiCategory === EmojiModel.Custom ? CustomEmojiModel : EmojiModel.emojis(_picker.emojiCategory)
|
|
||||||
|
|
||||||
delegate: QQC2.ItemDelegate {
|
|
||||||
width: Kirigami.Units.gridUnit * 2
|
|
||||||
height: Kirigami.Units.gridUnit * 2
|
|
||||||
|
|
||||||
contentItem: Kirigami.Heading {
|
|
||||||
level: 1
|
|
||||||
horizontalAlignment: Text.AlignHCenter
|
|
||||||
verticalAlignment: Text.AlignVCenter
|
|
||||||
font.family: 'emoji'
|
|
||||||
text: modelData.isCustom ? "" : modelData.unicode
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Image {
|
Kirigami.Separator {
|
||||||
visible: modelData.isCustom
|
visible: showQuickReaction
|
||||||
source: visible ? modelData.unicode : ""
|
Layout.fillWidth: true
|
||||||
anchors.fill: parent
|
Layout.preferredHeight: 1
|
||||||
anchors.margins: 2
|
|
||||||
|
|
||||||
sourceSize.width: width
|
|
||||||
sourceSize.height: height
|
|
||||||
|
|
||||||
Rectangle {
|
|
||||||
anchors.fill: parent
|
|
||||||
visible: parent.status === Image.Loading
|
|
||||||
radius: height/2
|
|
||||||
gradient: ShimmerGradient { }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onClicked: {
|
QQC2.ScrollView {
|
||||||
if (modelData.isCustom) {
|
visible: showQuickReaction
|
||||||
chosen(modelData.shortName)
|
Layout.fillWidth: true
|
||||||
} else {
|
Layout.preferredHeight: root.categoryIconSize + QQC2.ScrollBar.horizontal.height
|
||||||
chosen(modelData.unicode)
|
QQC2.ScrollBar.horizontal.height: QQC2.ScrollBar.horizontal.visible ? QQC2.ScrollBar.horizontal.implicitHeight : 0
|
||||||
}
|
|
||||||
emojiModel.emojiUsed(modelData)
|
ListView {
|
||||||
}
|
id: quickReactions
|
||||||
|
Layout.fillWidth: true
|
||||||
|
|
||||||
|
model: ["👍", "👎", "😄", "🎉", "😕", "❤", "🚀", "👀"]
|
||||||
|
|
||||||
|
delegate: EmojiDelegate {
|
||||||
|
emoji: modelData
|
||||||
|
|
||||||
|
height: root.categoryIconSize
|
||||||
|
width: height
|
||||||
|
|
||||||
|
onClicked: root.chosen(modelData)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
orientation: Qt.Horizontal
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
66
src/qml/Component/Emoji/EmojiTonesPicker.qml
Normal file
66
src/qml/Component/Emoji/EmojiTonesPicker.qml
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
QQC2.Popup {
|
||||||
|
id: tones
|
||||||
|
|
||||||
|
signal chosen(string emoji)
|
||||||
|
|
||||||
|
Component.onCompleted: {
|
||||||
|
tonesList.currentIndex = 0;
|
||||||
|
tonesList.forceActiveFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
required property string shortName
|
||||||
|
required property string unicode
|
||||||
|
required property int categoryIconSize
|
||||||
|
width: tones.categoryIconSize * tonesList.count + 2 * padding
|
||||||
|
height: tones.categoryIconSize + 2 * padding
|
||||||
|
y: -height
|
||||||
|
padding: 2
|
||||||
|
modal: true
|
||||||
|
dim: true
|
||||||
|
onOpened: x = Math.min(parent.mapFromGlobal(QQC2.Overlay.overlay.width - tones.width, 0).x, -(width - parent.width) / 2)
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
border.width: 1
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: tonesList
|
||||||
|
width: parent.width
|
||||||
|
height: parent.height
|
||||||
|
orientation: Qt.Horizontal
|
||||||
|
model: EmojiModel.tones(tones.shortName)
|
||||||
|
keyNavigationEnabled: true
|
||||||
|
keyNavigationWraps: true
|
||||||
|
|
||||||
|
delegate: EmojiDelegate {
|
||||||
|
id: emojiDelegate
|
||||||
|
checked: tonesList.currentIndex === model.index
|
||||||
|
emoji: modelData.unicode
|
||||||
|
name: modelData.shortName
|
||||||
|
|
||||||
|
width: tones.categoryIconSize
|
||||||
|
height: width
|
||||||
|
|
||||||
|
Keys.onEnterPressed: clicked()
|
||||||
|
Keys.onReturnPressed: clicked()
|
||||||
|
onClicked: {
|
||||||
|
tones.chosen(modelData.unicode)
|
||||||
|
EmojiModel.emojiUsed(modelData)
|
||||||
|
tones.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,8 +26,6 @@ QQC2.Popup {
|
|||||||
padding: 0
|
padding: 0
|
||||||
background: null
|
background: null
|
||||||
|
|
||||||
Keys.onBackPressed: root.close()
|
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|||||||
@@ -20,6 +20,9 @@ LoginStep {
|
|||||||
target: LoginHelper
|
target: LoginHelper
|
||||||
function onSsoUrlChanged() {
|
function onSsoUrlChanged() {
|
||||||
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
UrlHelper.openUrl(LoginHelper.ssoUrl)
|
||||||
|
root.showMessage(i18n("Complete the authentication steps in your browser"))
|
||||||
|
loginButton.enabled = true
|
||||||
|
loginButton.text = i18n("Login")
|
||||||
}
|
}
|
||||||
function onConnected() {
|
function onConnected() {
|
||||||
processed("qrc:/Loading.qml")
|
processed("qrc:/Loading.qml")
|
||||||
@@ -34,10 +37,12 @@ LoginStep {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
|
id: loginButton
|
||||||
text: i18n("Login")
|
text: i18n("Login")
|
||||||
onClicked: {
|
onClicked: {
|
||||||
LoginHelper.loginWithSso()
|
LoginHelper.loginWithSso()
|
||||||
root.showMessage(i18n("Complete the authentication steps in your browser"))
|
loginButton.enabled = false
|
||||||
|
loginButton.text = i18n("Loading…")
|
||||||
}
|
}
|
||||||
Component.onCompleted: forceActiveFocus()
|
Component.onCompleted: forceActiveFocus()
|
||||||
Keys.onReturnPressed: clicked()
|
Keys.onReturnPressed: clicked()
|
||||||
|
|||||||
@@ -40,6 +40,11 @@ QQC2.Popup {
|
|||||||
// we used to be able to expect that the text field wouldn't attempt to
|
// we used to be able to expect that the text field wouldn't attempt to
|
||||||
// perform a mini-DDOS attack using signals.
|
// perform a mini-DDOS attack using signals.
|
||||||
autoAccept: false
|
autoAccept: false
|
||||||
|
/**
|
||||||
|
* The focus is manged by the popup and we don't want to use the standard
|
||||||
|
* shortcut as it could block other SearchFields from using it.
|
||||||
|
*/
|
||||||
|
focusSequence: ""
|
||||||
|
|
||||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
|
Layout.preferredWidth: Kirigami.Units.gridUnit * 21 // 3 * 7 = 21, roughly 7 avatars on screen
|
||||||
Keys.onLeftPressed: cView.decrementCurrentIndex()
|
Keys.onLeftPressed: cView.decrementCurrentIndex()
|
||||||
|
|||||||
@@ -33,10 +33,7 @@ TimelineContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
innerObject: RowLayout {
|
innerObject: RowLayout {
|
||||||
|
Layout.maximumWidth: Math.min(fileDelegate.contentMaxWidth, implicitWidth)
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.maximumWidth: fileDelegate.contentMaxWidth
|
|
||||||
Layout.margins: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
@@ -111,24 +108,16 @@ TimelineContainer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
Layout.alignment: Qt.AlignVCenter
|
|
||||||
Layout.fillWidth: true
|
|
||||||
|
|
||||||
spacing: 0
|
spacing: 0
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
text: model.display
|
text: model.display
|
||||||
wrapMode: Text.Wrap
|
wrapMode: Text.Wrap
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: sizeLabel
|
id: sizeLabel
|
||||||
|
|
||||||
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
text: Controller.formatByteSize(content.info ? content.info.size : 0)
|
||||||
opacity: 0.7
|
opacity: 0.7
|
||||||
|
|
||||||
Layout.fillWidth: true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,10 +31,30 @@ TimelineContainer {
|
|||||||
innerObject: AnimatedImage {
|
innerObject: AnimatedImage {
|
||||||
id: img
|
id: img
|
||||||
|
|
||||||
|
property var imageWidth: {
|
||||||
|
if (imageDelegate.info.w > 0) {
|
||||||
|
return imageDelegate.info.w;
|
||||||
|
} else if (sourceSize.width > 0) {
|
||||||
|
return sourceSize.width;
|
||||||
|
} else {
|
||||||
|
return imageDelegate.contentMaxWidth;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
property var imageHeight: {
|
||||||
|
if (imageDelegate.info.h > 0) {
|
||||||
|
return imageDelegate.info.h;
|
||||||
|
} else if (sourceSize.height > 0) {
|
||||||
|
return sourceSize.height;
|
||||||
|
} else {
|
||||||
|
// Default to a 16:9 placeholder
|
||||||
|
return imageDelegate.contentMaxWidth / 16 * 9;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Layout.maximumWidth: Math.min(imageDelegate.contentMaxWidth, imageDelegate.maxWidth)
|
Layout.maximumWidth: Math.min(imageDelegate.contentMaxWidth, imageDelegate.maxWidth)
|
||||||
Layout.maximumHeight: Math.min(imageDelegate.contentMaxWidth / sourceSize.width * sourceSize.height, imageDelegate.maxWidth / sourceSize.width * sourceSize.height)
|
Layout.maximumHeight: Math.min(imageDelegate.contentMaxWidth / imageWidth * imageHeight, imageDelegate.maxWidth / imageWidth * imageHeight)
|
||||||
Layout.preferredWidth: imageDelegate.info.w > 0 ? imageDelegate.info.w : sourceSize.width
|
Layout.preferredWidth: imageWidth
|
||||||
Layout.preferredHeight: imageDelegate.info.h > 0 ? imageDelegate.info.h : sourceSize.height
|
Layout.preferredHeight: imageHeight
|
||||||
source: model.mediaUrl
|
source: model.mediaUrl
|
||||||
|
|
||||||
Image {
|
Image {
|
||||||
|
|||||||
@@ -82,12 +82,12 @@ Item {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
HoverHandler {
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
}
|
}
|
||||||
|
TapHandler {
|
||||||
MouseArea {
|
acceptedButtons: Qt.LeftButton
|
||||||
anchors.fill: parent
|
onTapped: replyComponent.replyClicked()
|
||||||
onClicked: {
|
|
||||||
replyComponent.replyClicked()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -97,6 +97,16 @@ Item {
|
|||||||
textMessage: reply.display
|
textMessage: reply.display
|
||||||
textFormat: Text.RichText
|
textFormat: Text.RichText
|
||||||
isReplyLabel: true
|
isReplyLabel: true
|
||||||
|
|
||||||
|
HoverHandler {
|
||||||
|
enabled: !hoveredLink
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
|
}
|
||||||
|
TapHandler {
|
||||||
|
enabled: !hoveredLink
|
||||||
|
acceptedButtons: Qt.LeftButton
|
||||||
|
onTapped: replyComponent.replyClicked()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Component {
|
Component {
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ QQC2.Control {
|
|||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
cursorShape: Qt.PointingHandCursor
|
||||||
onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
|
onClicked: userDetailDialog.createObject(QQC2.ApplicationWindow.overlay, {room: currentRoom, user: author.object, displayName: author.displayName, avatarMediaId: author.avatarMediaId, avatarUrl: author.avatarUrl}).open()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ ColumnLayout {
|
|||||||
|
|
||||||
default property alias innerObject : column.children
|
default property alias innerObject : column.children
|
||||||
|
|
||||||
property Item hoverComponent: hoverActions
|
property Item hoverComponent: hoverActions ?? null
|
||||||
property bool isEmote: false
|
property bool isEmote: false
|
||||||
property bool cardBackground: true
|
property bool cardBackground: true
|
||||||
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
|
property bool showUserMessageOnRight: Config.showLocalMessagesOnRight && model.author.isLocalUser && !Config.compactLayout
|
||||||
@@ -106,6 +106,9 @@ ColumnLayout {
|
|||||||
|
|
||||||
// Show hover actions by updating the global hover component to this delegate
|
// Show hover actions by updating the global hover component to this delegate
|
||||||
function updateHoverComponent() {
|
function updateHoverComponent() {
|
||||||
|
if (!hoverComponent) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (hovered && !Kirigami.Settings.isMobile) {
|
if (hovered && !Kirigami.Settings.isMobile) {
|
||||||
hoverComponent.delegate = root
|
hoverComponent.delegate = root
|
||||||
hoverComponent.bubble = bubble
|
hoverComponent.bubble = bubble
|
||||||
@@ -229,10 +232,10 @@ ColumnLayout {
|
|||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
id: timeLabel
|
id: timeLabel
|
||||||
|
|
||||||
text: visible ? time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
|
text: visible ? model.time.toLocaleTimeString(Qt.locale(), Locale.ShortFormat) : ""
|
||||||
color: Kirigami.Theme.disabledTextColor
|
color: Kirigami.Theme.disabledTextColor
|
||||||
QQC2.ToolTip.visible: hoverHandler.hovered
|
QQC2.ToolTip.visible: hoverHandler.hovered
|
||||||
QQC2.ToolTip.text: time.toLocaleString(Qt.locale(), Locale.LongFormat)
|
QQC2.ToolTip.text: model.time.toLocaleString(Qt.locale(), Locale.LongFormat)
|
||||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
|
||||||
HoverHandler {
|
HoverHandler {
|
||||||
@@ -268,6 +271,7 @@ ColumnLayout {
|
|||||||
id: bubbleBackground
|
id: bubbleBackground
|
||||||
visible: cardBackground && !Config.compactLayout
|
visible: cardBackground && !Config.compactLayout
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
color: {
|
color: {
|
||||||
if (model.author.isLocalUser) {
|
if (model.author.isLocalUser) {
|
||||||
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
return Kirigami.ColorUtils.tintWithAlpha(Kirigami.Theme.backgroundColor, Kirigami.Theme.highlightColor, 0.15)
|
||||||
|
|||||||
@@ -9,29 +9,54 @@ import org.kde.kirigami 2.15 as Kirigami
|
|||||||
import org.kde.neochat 1.0
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
QQC2.Popup {
|
QQC2.Popup {
|
||||||
id: root
|
id: emojiPopup
|
||||||
|
|
||||||
signal react(string emoji)
|
property bool includeCustom: false
|
||||||
|
property bool closeOnChosen: true
|
||||||
|
property bool showQuickReaction: false
|
||||||
|
|
||||||
|
signal chosen(string emoji)
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: RoomManager
|
||||||
|
function onCurrentRoomChanged() {
|
||||||
|
emojiPopup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onVisibleChanged: {
|
||||||
|
if (!visible) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
emojiPicker.forceActiveFocus()
|
||||||
|
}
|
||||||
|
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
radius: Kirigami.Units.smallSpacing
|
||||||
|
shadow.size: Kirigami.Units.smallSpacing
|
||||||
|
shadow.color: Qt.rgba(0.0, 0.0, 0.0, 0.10)
|
||||||
|
border.color: Kirigami.ColorUtils.tintWithAlpha(color, Kirigami.Theme.textColor, 0.15)
|
||||||
|
border.width: 2
|
||||||
|
}
|
||||||
|
|
||||||
modal: true
|
modal: true
|
||||||
focus: true
|
focus: true
|
||||||
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
|
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
|
||||||
margins: 0
|
margins: 0
|
||||||
padding: 1
|
padding: 2
|
||||||
implicitWidth: Kirigami.Units.gridUnit * 16
|
|
||||||
implicitHeight: Kirigami.Units.gridUnit * 20
|
|
||||||
|
|
||||||
background: Rectangle {
|
|
||||||
Kirigami.Theme.inherit: false
|
|
||||||
Kirigami.Theme.colorSet: Kirigami.Theme.View
|
|
||||||
color: Kirigami.Theme.backgroundColor
|
|
||||||
border.width: 1
|
|
||||||
border.color: Kirigami.ColorUtils.linearInterpolation(Kirigami.Theme.backgroundColor,
|
|
||||||
Kirigami.Theme.textColor,
|
|
||||||
0.15)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit * 20 + 2 * padding
|
||||||
|
width: Math.min(contentItem.categoryIconSize * contentItem.categoryCount + 2 * padding, QQC2.Overlay.overlay.width)
|
||||||
contentItem: EmojiPicker {
|
contentItem: EmojiPicker {
|
||||||
onChosen: react(emoji)
|
id: emojiPicker
|
||||||
|
height: 400
|
||||||
|
includeCustom: emojiPopup.includeCustom
|
||||||
|
showQuickReaction: emojiPopup.showQuickReaction
|
||||||
|
onChosen: {
|
||||||
|
emojiPopup.chosen(emoji)
|
||||||
|
if (emojiPopup.closeOnChosen) emojiPopup.close()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,9 +24,10 @@ Kirigami.OverlaySheet {
|
|||||||
|
|
||||||
parent: applicationWindow().overlay
|
parent: applicationWindow().overlay
|
||||||
|
|
||||||
leftPadding: 0
|
leftPadding: Kirigami.Units.smallSpacing
|
||||||
rightPadding: 0
|
rightPadding: Kirigami.Units.smallSpacing
|
||||||
topPadding: 0
|
topPadding: 0
|
||||||
|
bottomPadding: 0
|
||||||
|
|
||||||
title: i18nc("@title:menu Account detail dialog", "Account detail")
|
title: i18nc("@title:menu Account detail dialog", "Account detail")
|
||||||
|
|
||||||
@@ -36,8 +37,8 @@ Kirigami.OverlaySheet {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.topMargin: Kirigami.Units.smallSpacing
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
@@ -47,16 +48,6 @@ Kirigami.OverlaySheet {
|
|||||||
name: displayName
|
name: displayName
|
||||||
source: avatarMediaId ? ("image://mxc/" + avatarMediaId) : ""
|
source: avatarMediaId ? ("image://mxc/" + avatarMediaId) : ""
|
||||||
color: user.color
|
color: user.color
|
||||||
|
|
||||||
MouseArea {
|
|
||||||
anchors.fill: parent
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
if (avatarMediaId) {
|
|
||||||
fullScreenImage.createObject(parent, {filename: displayName, source: room.urlToMxcUrl(avatarUrl)}).showFullScreen()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -79,7 +70,9 @@ Kirigami.OverlaySheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.MenuSeparator {}
|
Kirigami.Separator {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
}
|
||||||
|
|
||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
visible: user !== room.localUser
|
visible: user !== room.localUser
|
||||||
@@ -104,6 +97,19 @@ Kirigami.OverlaySheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
visible: user !== room.localUser && room.canSendState("invite") && !room.containsUser(user.id)
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
enabled: !room.isUserBanned(user.id)
|
||||||
|
text: i18n("Invite this user")
|
||||||
|
icon.name: "list-add-user"
|
||||||
|
onTriggered: {
|
||||||
|
room.inviteToRoom(user.id)
|
||||||
|
root.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id)
|
visible: user !== room.localUser && room.canSendState("ban") && !room.isUserBanned(user.id)
|
||||||
|
|
||||||
@@ -169,11 +175,6 @@ Kirigami.OverlaySheet {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Component {
|
|
||||||
id: fullScreenImage
|
|
||||||
|
|
||||||
FullScreenImage {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onSheetOpenChanged: {
|
onSheetOpenChanged: {
|
||||||
|
|||||||
@@ -113,7 +113,7 @@ Loader {
|
|||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18n("Room Settings")
|
text: i18n("Room Settings")
|
||||||
icon.name: "configure"
|
icon.name: "configure"
|
||||||
onTriggered: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
|
onTriggered: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Room Settings") })
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.MenuSeparator {}
|
QQC2.MenuSeparator {}
|
||||||
@@ -180,7 +180,7 @@ Loader {
|
|||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
icon.name: 'settings-configure'
|
icon.name: 'settings-configure'
|
||||||
onClicked: {
|
onClicked: {
|
||||||
QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
|
QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Room Settings") })
|
||||||
drawer.close()
|
drawer.close()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,11 @@ Loader {
|
|||||||
Component {
|
Component {
|
||||||
id: regularMenu
|
id: regularMenu
|
||||||
QQC2.Menu {
|
QQC2.Menu {
|
||||||
|
QQC2.MenuItem {
|
||||||
|
text: i18nc("'Space' is a matrix space", "View Space")
|
||||||
|
onTriggered: RoomManager.enterRoom(room);
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18nc("@action:inmenu", "Copy Address to Clipboard")
|
text: i18nc("@action:inmenu", "Copy Address to Clipboard")
|
||||||
onTriggered: if (room.canonicalAlias.length === 0) {
|
onTriggered: if (room.canonicalAlias.length === 0) {
|
||||||
@@ -32,7 +37,7 @@ Loader {
|
|||||||
|
|
||||||
QQC2.MenuItem {
|
QQC2.MenuItem {
|
||||||
text: i18nc("'Space' is a matrix space", "Space Settings")
|
text: i18nc("'Space' is a matrix space", "Space Settings")
|
||||||
onTriggered: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
|
onTriggered: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Space Settings") })
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.MenuSeparator {}
|
QQC2.MenuSeparator {}
|
||||||
@@ -89,7 +94,7 @@ Loader {
|
|||||||
|
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
icon.name: 'settings-configure'
|
icon.name: 'settings-configure'
|
||||||
onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
|
onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Space Settings") })
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Kirigami.BasicListItem {
|
Kirigami.BasicListItem {
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Kirigami.ScrollablePage {
|
|||||||
onAccepted: userDictListModel.search()
|
onAccepted: userDictListModel.search()
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
QQC2.Button {
|
||||||
visible: identifierField.isUserID
|
visible: identifierField.isUserID
|
||||||
|
|
||||||
text: i18n("Add")
|
text: i18n("Add")
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Kirigami.ScrollablePage {
|
|||||||
clip: true
|
clip: true
|
||||||
visible: spaceList.count > 0
|
visible: spaceList.count > 0
|
||||||
|
|
||||||
Layout.preferredHeight: Kirigami.Units.gridUnit * 3
|
Layout.preferredHeight: Kirigami.Units.gridUnit * 2
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
model: SortFilterSpaceListModel {
|
model: SortFilterSpaceListModel {
|
||||||
@@ -39,14 +39,18 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
header: QQC2.Control {
|
header: QQC2.ItemDelegate {
|
||||||
contentItem: QQC2.RoundButton {
|
|
||||||
id: homeButton
|
id: homeButton
|
||||||
flat: true
|
|
||||||
padding: Kirigami.Units.gridUnit / 2
|
|
||||||
icon.name: "home"
|
icon.name: "home"
|
||||||
text: i18nc("@action:button", "Show All Rooms")
|
text: i18nc("@action:button", "Show All Rooms")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
height: parent.height
|
||||||
|
width: height
|
||||||
|
leftPadding: topPadding
|
||||||
|
rightPadding: topPadding
|
||||||
|
|
||||||
|
contentItem: Kirigami.Icon {
|
||||||
|
source: "home"
|
||||||
|
}
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
sortFilterRoomListModel.activeSpaceId = "";
|
sortFilterRoomListModel.activeSpaceId = "";
|
||||||
@@ -54,11 +58,9 @@ Kirigami.ScrollablePage {
|
|||||||
listView.positionViewAtIndex(0, ListView.Beginning);
|
listView.positionViewAtIndex(0, ListView.Beginning);
|
||||||
}
|
}
|
||||||
|
|
||||||
QQC2.ToolTip {
|
QQC2.ToolTip.text: homeButton.text
|
||||||
text: homeButton.text
|
QQC2.ToolTip.visible: hovered
|
||||||
}
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
Accessible.name: text
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delegate: QQC2.ItemDelegate {
|
delegate: QQC2.ItemDelegate {
|
||||||
@@ -84,9 +86,9 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
Accessible.name: currentRoom.displayName
|
Accessible.name: currentRoom.displayName
|
||||||
|
|
||||||
QQC2.ToolTip {
|
QQC2.ToolTip.text: currentRoom.displayName
|
||||||
text: currentRoom.displayName
|
QQC2.ToolTip.visible: hovered
|
||||||
}
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
|
||||||
onPressAndHold: {
|
onPressAndHold: {
|
||||||
spaceList.createContextMenu(currentRoom)
|
spaceList.createContextMenu(currentRoom)
|
||||||
@@ -116,9 +118,7 @@ Kirigami.ScrollablePage {
|
|||||||
title: i18n("Rooms")
|
title: i18n("Rooms")
|
||||||
|
|
||||||
property var enteredRoom
|
property var enteredRoom
|
||||||
property bool collapsedMode: Config.roomListPageWidth === applicationWindow().collapsedPageWidth && applicationWindow().shouldUseSidebars
|
property bool collapsedMode: Config.roomListPageWidth < applicationWindow().minPageWidth && applicationWindow().shouldUseSidebars
|
||||||
|
|
||||||
verticalScrollBarPolicy: collapsedMode ? QQC2.ScrollBar.AlwaysOff : QQC2.ScrollBar.AsNeeded
|
|
||||||
|
|
||||||
onCollapsedModeChanged: if (collapsedMode) {
|
onCollapsedModeChanged: if (collapsedMode) {
|
||||||
sortFilterRoomListModel.filterText = "";
|
sortFilterRoomListModel.filterText = "";
|
||||||
@@ -180,7 +180,7 @@ Kirigami.ScrollablePage {
|
|||||||
leftPadding: Kirigami.Units.largeSpacing
|
leftPadding: Kirigami.Units.largeSpacing
|
||||||
rightPadding: Kirigami.Units.largeSpacing
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
width: visible ? page.width : 0
|
width: visible ? ListView.view.width : 0
|
||||||
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
height: visible ? Kirigami.Units.gridUnit * 2 : 0
|
||||||
|
|
||||||
Kirigami.Icon {
|
Kirigami.Icon {
|
||||||
@@ -234,21 +234,46 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
section.property: sortFilterRoomListModel.filterText.length === 0 && !Config.mergeRoomList ? "category" : null
|
||||||
section.delegate: Kirigami.ListSectionHeader {
|
section.delegate: page.collapsedMode ? foldButton : sectionHeader
|
||||||
|
|
||||||
|
Component {
|
||||||
id: sectionHeader
|
id: sectionHeader
|
||||||
|
Kirigami.ListSectionHeader {
|
||||||
height: implicitHeight
|
height: implicitHeight
|
||||||
label: roomListModel.categoryName(section)
|
label: roomListModel.categoryName(section)
|
||||||
action: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
onTriggered: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||||
}
|
}
|
||||||
contentItem.children: QQC2.ToolButton {
|
contentItem.children: QQC2.ToolButton {
|
||||||
icon.name: page.collapsedMode ? roomListModel.categoryIconName(section) : (roomListModel.categoryVisible(section) ? "go-up" : "go-down")
|
icon.name: (roomListModel.categoryVisible(section) ? "go-up" : "go-down")
|
||||||
icon.width: Kirigami.Units.iconSizes.small
|
icon.width: Kirigami.Units.iconSizes.small
|
||||||
icon.height: Kirigami.Units.iconSizes.small
|
icon.height: Kirigami.Units.iconSizes.small
|
||||||
|
|
||||||
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
Component {
|
||||||
|
id: foldButton
|
||||||
|
Item {
|
||||||
|
width: ListView.view.width
|
||||||
|
height: visible ? width : 0
|
||||||
|
QQC2.ToolButton {
|
||||||
|
id: button
|
||||||
|
anchors.centerIn: parent
|
||||||
|
|
||||||
|
icon.name: hovered ? (roomListModel.categoryVisible(section) ? "go-up" : "go-down") : roomListModel.categoryIconName(section)
|
||||||
|
icon.width: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
icon.height: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
|
||||||
|
onClicked: roomListModel.setCategoryVisible(section, !roomListModel.categoryVisible(section))
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: roomListModel.categoryName(section)
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reuseItems: true
|
reuseItems: true
|
||||||
currentIndex: -1 // we don't want any room highlighted by default
|
currentIndex: -1 // we don't want any room highlighted by default
|
||||||
@@ -272,7 +297,8 @@ Kirigami.ScrollablePage {
|
|||||||
rightPadding: Kirigami.Units.largeSpacing
|
rightPadding: Kirigami.Units.largeSpacing
|
||||||
bottomPadding: Kirigami.Units.largeSpacing
|
bottomPadding: Kirigami.Units.largeSpacing
|
||||||
width: ListView.view.width
|
width: ListView.view.width
|
||||||
height: ListView.view.width
|
height: visible ? ListView.view.width : 0
|
||||||
|
visible: model.categoryVisible || sortFilterRoomListModel.filterText.length > 0 || Config.mergeRoomList
|
||||||
|
|
||||||
contentItem: Kirigami.Avatar {
|
contentItem: Kirigami.Avatar {
|
||||||
source: avatar ? "image://mxc/" + avatar : ""
|
source: avatar ? "image://mxc/" + avatar : ""
|
||||||
@@ -316,6 +342,7 @@ Kirigami.ScrollablePage {
|
|||||||
labelItem.textFormat: Text.PlainText
|
labelItem.textFormat: Text.PlainText
|
||||||
subtitle: subtitleText
|
subtitle: subtitleText
|
||||||
subtitleItem.textFormat: Text.PlainText
|
subtitleItem.textFormat: Text.PlainText
|
||||||
|
subtitleItem.visible: !Config.compactRoomList
|
||||||
onPressAndHold: {
|
onPressAndHold: {
|
||||||
createRoomListContextMenu()
|
createRoomListContextMenu()
|
||||||
}
|
}
|
||||||
@@ -329,7 +356,7 @@ Kirigami.ScrollablePage {
|
|||||||
source: avatar ? "image://mxc/" + avatar : ""
|
source: avatar ? "image://mxc/" + avatar : ""
|
||||||
name: model.name || i18n("No Name")
|
name: model.name || i18n("No Name")
|
||||||
implicitWidth: visible ? height : 0
|
implicitWidth: visible ? height : 0
|
||||||
visible: Config.showAvatarInTimeline
|
visible: Config.showAvatarInRoomDrawer
|
||||||
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
|
||||||
}
|
}
|
||||||
@@ -345,11 +372,13 @@ Kirigami.ScrollablePage {
|
|||||||
Accessible.name: i18n("Muted room")
|
Accessible.name: i18n("Muted room")
|
||||||
}
|
}
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
|
id: notificationCountLabel
|
||||||
text: notificationCount > 0 ? notificationCount : "●"
|
text: notificationCount > 0 ? notificationCount : "●"
|
||||||
visible: unreadCount > 0
|
visible: unreadCount > 0
|
||||||
padding: Kirigami.Units.smallSpacing
|
|
||||||
color: Kirigami.Theme.textColor
|
color: Kirigami.Theme.textColor
|
||||||
Layout.minimumWidth: height
|
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.minimumHeight: Kirigami.Units.iconSizes.smallMedium
|
||||||
|
Layout.minimumWidth: Math.max(notificationCountTextMetrics.advanceWidth + Kirigami.Units.smallSpacing * 2, height)
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
background: Rectangle {
|
background: Rectangle {
|
||||||
visible: notificationCount > 0
|
visible: notificationCount > 0
|
||||||
@@ -358,10 +387,15 @@ Kirigami.ScrollablePage {
|
|||||||
opacity: highlightCount > 0 ? 1 : 0.3
|
opacity: highlightCount > 0 ? 1 : 0.3
|
||||||
radius: height / 2
|
radius: height / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TextMetrics {
|
||||||
|
id: notificationCountTextMetrics
|
||||||
|
text: notificationCountLabel.text
|
||||||
|
}
|
||||||
}
|
}
|
||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
id: configButton
|
id: configButton
|
||||||
visible: roomListItem.hovered
|
visible: roomListItem.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList
|
||||||
Accessible.name: i18n("Configure room")
|
Accessible.name: i18n("Configure room")
|
||||||
|
|
||||||
action: Kirigami.Action {
|
action: Kirigami.Action {
|
||||||
@@ -376,11 +410,13 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
function createRoomListContextMenu() {
|
function createRoomListContextMenu() {
|
||||||
const menu = roomListContextMenu.createObject(page, {room: currentRoom})
|
const menu = roomListContextMenu.createObject(page, {room: currentRoom})
|
||||||
|
if (!Kirigami.Settings.isMobile && !Config.compactRoomList) {
|
||||||
configButton.visible = true
|
configButton.visible = true
|
||||||
configButton.down = true
|
configButton.down = true
|
||||||
|
}
|
||||||
menu.closed.connect(function() {
|
menu.closed.connect(function() {
|
||||||
configButton.down = undefined
|
configButton.down = undefined
|
||||||
configButton.visible = Qt.binding(function() { return roomListItem.hovered || Kirigami.Settings.isMobile })
|
configButton.visible = Qt.binding(function() { return roomListItem.hovered && !Kirigami.Settings.isMobile && !Config.compactRoomList })
|
||||||
})
|
})
|
||||||
menu.open()
|
menu.open()
|
||||||
}
|
}
|
||||||
@@ -390,5 +426,6 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
footer: UserInfo {
|
footer: UserInfo {
|
||||||
width: parent.width
|
width: parent.width
|
||||||
|
visible: !page.collapsedMode
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
onCurrentRoomChanged: {
|
onCurrentRoomChanged: {
|
||||||
applicationWindow().hoverLinkIndicator.text = "";
|
applicationWindow().hoverLinkIndicator.text = "";
|
||||||
|
messageListView.positionViewAtBeginning();
|
||||||
hasScrolledUpBefore = false;
|
hasScrolledUpBefore = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,7 +261,7 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
maxWidth: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.width - Kirigami.Units.largeSpacing * 2 : 0
|
maxWidth: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.width - Kirigami.Units.largeSpacing * 2 : 0
|
||||||
z: 3
|
z: 3
|
||||||
visible: messageListView.sectionBannerItem && messageListView.sectionBannerItem.ListView.section != ""
|
visible: messageListView.sectionBannerItem != undefined && messageListView.sectionBannerItem.ListView.section != ""
|
||||||
labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : ""
|
labelText: messageListView.sectionBannerItem ? messageListView.sectionBannerItem.ListView.section : ""
|
||||||
}
|
}
|
||||||
footerPositioning: ListView.OverlayHeader
|
footerPositioning: ListView.OverlayHeader
|
||||||
@@ -521,7 +522,8 @@ Kirigami.ScrollablePage {
|
|||||||
onClicked: emojiDialog.open();
|
onClicked: emojiDialog.open();
|
||||||
EmojiDialog {
|
EmojiDialog {
|
||||||
id: emojiDialog
|
id: emojiDialog
|
||||||
onReact: {
|
showQuickReaction: true
|
||||||
|
onChosen: {
|
||||||
page.currentRoom.toggleReaction(hoverActions.event.eventId, emoji);
|
page.currentRoom.toggleReaction(hoverActions.event.eventId, emoji);
|
||||||
chatBox.focusInputField();
|
chatBox.focusInputField();
|
||||||
}
|
}
|
||||||
|
|||||||
75
src/qml/Page/SearchPage.qml
Normal file
75
src/qml/Page/SearchPage.qml
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2022 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.20 as Kirigami
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
id: searchPage
|
||||||
|
|
||||||
|
property var currentRoom
|
||||||
|
|
||||||
|
title: i18nc("@action:title", "Search Messages")
|
||||||
|
|
||||||
|
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||||
|
|
||||||
|
SearchModel {
|
||||||
|
id: searchModel
|
||||||
|
connection: Controller.activeConnection
|
||||||
|
searchText: searchField.text
|
||||||
|
room: searchPage.currentRoom
|
||||||
|
}
|
||||||
|
|
||||||
|
header: RowLayout {
|
||||||
|
Kirigami.SearchField {
|
||||||
|
id: searchField
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.leftMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Keys.onEnterPressed: searchButton.clicked()
|
||||||
|
Keys.onReturnPressed: searchButton.clicked()
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
id: searchButton
|
||||||
|
Layout.topMargin: Kirigami.Units.smallSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.smallSpacing
|
||||||
|
onClicked: searchModel.search()
|
||||||
|
icon.name: "search"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: messageListView
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.fillHeight: true
|
||||||
|
spacing: 0
|
||||||
|
verticalLayoutDirection: ListView.BottomToTop
|
||||||
|
|
||||||
|
section.property: "section"
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: searchField.text.length === 0 && messageListView.count === 0
|
||||||
|
text: i18n("Enter a text to start searching")
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.PlaceholderMessage {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: searchField.text.length > 0 && messageListView.count === 0 && !searchModel.searching
|
||||||
|
text: i18n("No results found")
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.LoadingPlaceholder {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
visible: searchModel.searching
|
||||||
|
}
|
||||||
|
|
||||||
|
model: searchModel
|
||||||
|
delegate: EventDelegate {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -75,7 +75,7 @@ Kirigami.OverlayDrawer {
|
|||||||
id: columnLayout
|
id: columnLayout
|
||||||
property alias userSearchText: userListSearchField.text
|
property alias userSearchText: userListSearchField.text
|
||||||
property alias highlightedUser: userListView.currentIndex
|
property alias highlightedUser: userListView.currentIndex
|
||||||
spacing: 0
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
Kirigami.AbstractApplicationHeader {
|
Kirigami.AbstractApplicationHeader {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -86,94 +86,19 @@ Kirigami.OverlayDrawer {
|
|||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
anchors.fill: parent
|
anchors.fill: parent
|
||||||
spacing: Kirigami.Units.smallSpacing
|
|
||||||
|
|
||||||
Kirigami.Heading {
|
Kirigami.Heading {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: i18n("Room information")
|
text: i18n("Room information")
|
||||||
level: 1
|
level: 1
|
||||||
}
|
}
|
||||||
QQC2.ToolButton {
|
|
||||||
id: devtoolsButton
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
icon.name: "tools"
|
|
||||||
text: i18n("Open developer tools")
|
|
||||||
display: QQC2.AbstractButton.IconOnly
|
|
||||||
visible: Config.developerTools && Controller.quotientMinorVersion > 6
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
applicationWindow().pageStack.layers.push("qrc:/DevtoolsPage.qml", {room: room}, {title: i18n("Developer Tools")})
|
|
||||||
roomDrawer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ToolTip {
|
|
||||||
text: devtoolsButton.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.ToolButton {
|
|
||||||
id: inviteButton
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
icon.name: "list-add-user"
|
|
||||||
text: i18n("Invite user to room")
|
|
||||||
display: QQC2.AbstractButton.IconOnly
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
applicationWindow().pageStack.layers.push("qrc:/InviteUserPage.qml", {room: room})
|
|
||||||
roomDrawer.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ToolTip {
|
|
||||||
text: inviteButton.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.ToolButton {
|
|
||||||
id: favouriteButton
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
icon.name: room && room.isFavourite ? "rating" : "rating-unrated"
|
|
||||||
checkable: true
|
|
||||||
checked: room && room.isFavourite
|
|
||||||
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
|
||||||
display: QQC2.AbstractButton.IconOnly
|
|
||||||
|
|
||||||
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
|
||||||
|
|
||||||
QQC2.ToolTip {
|
|
||||||
text: favouriteButton.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.ToolButton {
|
|
||||||
id: encryptButton
|
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight
|
|
||||||
icon.name: 'channel-insecure-symbolic'
|
|
||||||
enabled: roomDrawer.room.canEncryptRoom
|
|
||||||
visible: !roomDrawer.room.usesEncryption && Controller.encryptionSupported
|
|
||||||
text: i18n("Enable encryption")
|
|
||||||
display: QQC2.AbstractButton.IconOnly
|
|
||||||
|
|
||||||
onClicked: {
|
|
||||||
let dialog = confirmEncryptionDialog.createObject(applicationWindow(), {room: roomDrawer.room});
|
|
||||||
roomDrawer.close();
|
|
||||||
dialog.open();
|
|
||||||
}
|
|
||||||
|
|
||||||
QQC2.ToolTip {
|
|
||||||
text: encryptButton.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QQC2.ToolButton {
|
QQC2.ToolButton {
|
||||||
id: settingsButton
|
id: settingsButton
|
||||||
|
|
||||||
Layout.alignment: Qt.AlignRight
|
icon.name: "settings-configure"
|
||||||
icon.name: 'settings-configure'
|
|
||||||
text: i18n("Room settings")
|
text: i18n("Room settings")
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
|
||||||
onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room})
|
onClicked: QQC2.ApplicationWindow.window.pageStack.pushDialogLayer('qrc:/Categories.qml', {room: room}, { title: i18n("Room Settings") })
|
||||||
|
|
||||||
QQC2.ToolTip {
|
QQC2.ToolTip {
|
||||||
text: settingsButton.text
|
text: settingsButton.text
|
||||||
}
|
}
|
||||||
@@ -181,14 +106,9 @@ Kirigami.OverlayDrawer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
|
||||||
Layout.fillWidth: true
|
|
||||||
Layout.margins: Kirigami.Units.largeSpacing
|
|
||||||
spacing: Kirigami.Units.largeSpacing
|
|
||||||
|
|
||||||
RowLayout {
|
RowLayout {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: Kirigami.Units.smallSpacing
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
spacing: Kirigami.Units.largeSpacing
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
|
||||||
Kirigami.Avatar {
|
Kirigami.Avatar {
|
||||||
@@ -197,6 +117,23 @@ Kirigami.OverlayDrawer {
|
|||||||
|
|
||||||
name: room ? room.displayName : ""
|
name: room ? room.displayName : ""
|
||||||
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
source: room ? ("image://mxc/" + room.avatarMediaId) : ""
|
||||||
|
|
||||||
|
Rectangle {
|
||||||
|
visible: room.usesEncryption
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
|
width: Kirigami.Units.gridUnit
|
||||||
|
height: Kirigami.Units.gridUnit
|
||||||
|
anchors.bottom: parent.bottom
|
||||||
|
anchors.right: parent.right
|
||||||
|
|
||||||
|
radius: width / 2
|
||||||
|
|
||||||
|
Kirigami.Icon {
|
||||||
|
source: "channel-secure-symbolic"
|
||||||
|
anchors.fill: parent
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
@@ -227,6 +164,8 @@ Kirigami.OverlayDrawer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TextEdit {
|
TextEdit {
|
||||||
|
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
text: room && room.topic ? room.topic.replace(replaceLinks, "<a href=\"$1\">$1</a>") : i18n("No Topic")
|
||||||
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
readonly property var replaceLinks: /(http[s]?:\/\/[^ \r\n]*)/g
|
||||||
@@ -244,11 +183,74 @@ Kirigami.OverlayDrawer {
|
|||||||
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
cursorShape: parent.hoveredLink ? Qt.PointingHandCursor : Qt.IBeamCursor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kirigami.ListSectionHeader {
|
||||||
|
label: i18n("Options")
|
||||||
|
activeFocusOnTab: false
|
||||||
|
}
|
||||||
|
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: devtoolsButton
|
||||||
|
|
||||||
|
icon: "tools"
|
||||||
|
text: i18n("Open developer tools")
|
||||||
|
visible: Config.developerTools
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
applicationWindow().pageStack.layers.push("qrc:/DevtoolsPage.qml", {room: room}, {title: i18n("Developer Tools")})
|
||||||
|
roomDrawer.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: searchButton
|
||||||
|
|
||||||
|
icon: "search"
|
||||||
|
text: i18n("Search in this room")
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
pageStack.pushDialogLayer("qrc:/SearchPage.qml", {
|
||||||
|
currentRoom: room
|
||||||
|
}, {
|
||||||
|
title: i18nc("@action:title", "Search")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.BasicListItem {
|
||||||
|
id: favouriteButton
|
||||||
|
|
||||||
|
icon: room && room.isFavourite ? "rating" : "rating-unrated"
|
||||||
|
text: room && room.isFavourite ? i18n("Remove room from favorites") : i18n("Make room favorite")
|
||||||
|
|
||||||
|
onClicked: room.isFavourite ? room.removeTag("m.favourite") : room.addTag("m.favourite", 1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.ListSectionHeader {
|
Kirigami.ListSectionHeader {
|
||||||
label: i18n("Members")
|
label: i18n("Members")
|
||||||
activeFocusOnTab: false
|
activeFocusOnTab: false
|
||||||
|
spacing: 0
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
id: memberSearchToggle
|
||||||
|
checkable: true
|
||||||
|
icon.name: "search"
|
||||||
|
QQC2.ToolTip.text: i18n("Search user in room")
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolButton {
|
||||||
|
visible: roomDrawer.room.canSendState("invite")
|
||||||
|
icon.name: "list-add-user"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
applicationWindow().pageStack.layers.push("qrc:/InviteUserPage.qml", {room: roomDrawer.room})
|
||||||
|
roomDrawer.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip.text: i18n("Invite user to room")
|
||||||
|
QQC2.ToolTip.visible: hovered
|
||||||
|
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
|
||||||
QQC2.Label {
|
QQC2.Label {
|
||||||
Layout.alignment: Qt.AlignRight
|
Layout.alignment: Qt.AlignRight
|
||||||
@@ -258,12 +260,15 @@ Kirigami.OverlayDrawer {
|
|||||||
|
|
||||||
Kirigami.SearchField {
|
Kirigami.SearchField {
|
||||||
id: userListSearchField
|
id: userListSearchField
|
||||||
|
visible: memberSearchToggle.checked
|
||||||
|
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
Layout.leftMargin: Kirigami.Units.largeSpacing - 1
|
Layout.leftMargin: Kirigami.Units.largeSpacing - 1
|
||||||
Layout.rightMargin: Kirigami.Units.largeSpacing - 1
|
Layout.rightMargin: Kirigami.Units.largeSpacing - 1
|
||||||
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
Layout.bottomMargin: Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
focusSequence: "Ctrl+Shift+F"
|
||||||
|
|
||||||
onAccepted: sortedMessageEventModel.filterString = text;
|
onAccepted: sortedMessageEventModel.filterString = text;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -361,10 +366,4 @@ Kirigami.OverlayDrawer {
|
|||||||
|
|
||||||
UserDetailDialog {}
|
UserDetailDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component {
|
|
||||||
id: confirmEncryptionDialog
|
|
||||||
|
|
||||||
ConfirmEncryptionDialog {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,16 @@ Kirigami.CategorizedSettings {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
Kirigami.SettingAction {
|
||||||
|
text: i18n("Permissions")
|
||||||
|
icon.name: "visibility"
|
||||||
|
page: Qt.resolvedUrl("Permissions.qml")
|
||||||
|
initialProperties: {
|
||||||
|
return {
|
||||||
|
room: root.room
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
Kirigami.SettingAction {
|
Kirigami.SettingAction {
|
||||||
text: i18n("Notifications")
|
text: i18n("Notifications")
|
||||||
icon.name: "notifications"
|
icon.name: "notifications"
|
||||||
|
|||||||
@@ -119,6 +119,33 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MobileForm.FormTextDelegate {
|
||||||
|
text: i18n("Room ID")
|
||||||
|
description: room.id
|
||||||
|
}
|
||||||
|
MobileForm.FormTextDelegate {
|
||||||
|
text: i18n("Room version")
|
||||||
|
description: room.version
|
||||||
|
|
||||||
|
contentItem.children: QQC2.Button {
|
||||||
|
visible: room.canSwitchVersions()
|
||||||
|
enabled: room.version < room.maxRoomVersion
|
||||||
|
text: i18n("Upgrade Room")
|
||||||
|
icon.name: "arrow-up-double"
|
||||||
|
|
||||||
|
onClicked: {
|
||||||
|
if (room.canSwitchVersions()) {
|
||||||
|
roomUpgradeSheet.currentRoomVersion = room.version
|
||||||
|
roomUpgradeSheet.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
QQC2.ToolTip {
|
||||||
|
text: text
|
||||||
|
delay: Kirigami.Units.toolTipDelay
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MobileForm.FormCard {
|
MobileForm.FormCard {
|
||||||
@@ -176,6 +203,7 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
MobileForm.AbstractFormDelegate {
|
MobileForm.AbstractFormDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
visible: room.canSendState("m.room.canonical_alias")
|
||||||
|
|
||||||
contentItem : RowLayout {
|
contentItem : RowLayout {
|
||||||
Kirigami.ActionTextField {
|
Kirigami.ActionTextField {
|
||||||
@@ -200,7 +228,7 @@ Kirigami.ScrollablePage {
|
|||||||
QQC2.Button {
|
QQC2.Button {
|
||||||
id: addButton
|
id: addButton
|
||||||
|
|
||||||
text: i18n("Add keyword")
|
text: i18n("Add new alias")
|
||||||
Accessible.name: text
|
Accessible.name: text
|
||||||
icon.name: "list-add"
|
icon.name: "list-add"
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
@@ -255,6 +283,30 @@ Kirigami.ScrollablePage {
|
|||||||
|
|
||||||
OpenFileDialog {}
|
OpenFileDialog {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Kirigami.OverlaySheet {
|
||||||
|
id: roomUpgradeSheet
|
||||||
|
|
||||||
|
property var currentRoomVersion
|
||||||
|
|
||||||
|
title: i18n("Upgrade the Room")
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
QQC2.SpinBox {
|
||||||
|
id: spinBox
|
||||||
|
Kirigami.FormData.label: i18n("Select new version")
|
||||||
|
from: room.version
|
||||||
|
to: room.maxRoomVersion
|
||||||
|
value: room.version
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18n("Confirm")
|
||||||
|
onClicked: {
|
||||||
|
room.switchVersion(spinBox.value)
|
||||||
|
roomUpgradeSheet.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
453
src/qml/RoomSettings/Permissions.qml
Normal file
453
src/qml/RoomSettings/Permissions.qml
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
// 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
|
||||||
|
|
||||||
|
import QtQuick 2.15
|
||||||
|
import QtQuick.Controls 2.15 as QQC2
|
||||||
|
import QtQuick.Layouts 1.15
|
||||||
|
|
||||||
|
import org.kde.kirigami 2.15 as Kirigami
|
||||||
|
import org.kde.kirigamiaddons.labs.mobileform 0.1 as MobileForm
|
||||||
|
import org.kde.kitemmodels 1.0
|
||||||
|
|
||||||
|
import org.kde.neochat 1.0
|
||||||
|
|
||||||
|
Kirigami.ScrollablePage {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
property var room
|
||||||
|
|
||||||
|
title: i18nc('@title:window', 'Permissions')
|
||||||
|
|
||||||
|
leftPadding: 0
|
||||||
|
rightPadding: 0
|
||||||
|
|
||||||
|
UserListModel {
|
||||||
|
id: userListModel
|
||||||
|
room: root.room
|
||||||
|
}
|
||||||
|
|
||||||
|
ListModel {
|
||||||
|
id: powerLevelModel
|
||||||
|
ListElement {text: "Member (0)"; powerLevel: 0}
|
||||||
|
ListElement {text: "Moderator (50)"; powerLevel: 50}
|
||||||
|
ListElement {text: "Admin (100)"; powerLevel: 100}
|
||||||
|
}
|
||||||
|
|
||||||
|
ColumnLayout {
|
||||||
|
MobileForm.FormCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
MobileForm.FormCardHeader {
|
||||||
|
title: i18n("Privileged Users")
|
||||||
|
}
|
||||||
|
Repeater {
|
||||||
|
model: KSortFilterProxyModel {
|
||||||
|
sourceModel: userListModel
|
||||||
|
sortRole: "perm"
|
||||||
|
filterRowCallback: function(source_row, source_parent) {
|
||||||
|
let permRole = sourceModel.data(sourceModel.index(source_row, 0, source_parent), Qt.UserRole + 5)
|
||||||
|
return permRole != UserType.Muted && permRole != UserType.Member;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
delegate: MobileForm.FormTextDelegate {
|
||||||
|
text: name
|
||||||
|
description: userId
|
||||||
|
contentItem.children: RowLayout {
|
||||||
|
spacing: Kirigami.Units.largeSpacing
|
||||||
|
QQC2.Label {
|
||||||
|
visible: !room.canSendState("m.room.power_levels")
|
||||||
|
text: {
|
||||||
|
switch (perm) {
|
||||||
|
case UserType.Owner:
|
||||||
|
return i18n("Owner");
|
||||||
|
case UserType.Admin:
|
||||||
|
return i18n("Admin");
|
||||||
|
case UserType.Moderator:
|
||||||
|
return i18n("Mod");
|
||||||
|
case UserType.Muted:
|
||||||
|
return i18n("Muted");
|
||||||
|
case UserType.Member:
|
||||||
|
return i18n("Member");
|
||||||
|
default:
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
}
|
||||||
|
QQC2.ComboBox {
|
||||||
|
focusPolicy: Qt.NoFocus // provided by parent
|
||||||
|
model: powerLevelModel
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
visible: room.canSendState("m.room.power_levels")
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(powerLevel)
|
||||||
|
onActivated: {
|
||||||
|
room.setUserPowerLevel(userId, currentValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MobileForm.FormDelegateSeparator { below: userListSearchCard }
|
||||||
|
MobileForm.AbstractFormDelegate {
|
||||||
|
id: userListSearchCard
|
||||||
|
Layout.fillWidth: true
|
||||||
|
visible: room.canSendState("m.room.power_levels")
|
||||||
|
|
||||||
|
contentItem: Kirigami.SearchField {
|
||||||
|
id: userListSearchField
|
||||||
|
Layout.fillWidth: true
|
||||||
|
autoAccept: false
|
||||||
|
|
||||||
|
Keys.onUpPressed: userListView.decrementCurrentIndex()
|
||||||
|
Keys.onDownPressed: userListView.incrementCurrentIndex()
|
||||||
|
|
||||||
|
onAccepted: {
|
||||||
|
let currentUser = userListView.itemAtIndex(userListView.currentIndex);
|
||||||
|
currentUser.action.trigger();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QQC2.Popup {
|
||||||
|
id: userListSearchPopup
|
||||||
|
|
||||||
|
x: userListSearchField.x
|
||||||
|
y: userListSearchField.y - height
|
||||||
|
width: userListSearchField.width
|
||||||
|
height: {
|
||||||
|
let maxHeight = userListSearchField.mapToGlobal(userListSearchField.x, userListSearchField.y).y - Kirigami.Units.largeSpacing * 3;
|
||||||
|
let minHeight = Kirigami.Units.gridUnit * 2 + userListSearchPopup.padding * 2;
|
||||||
|
let filterContentHeight = userListView.contentHeight + userListSearchPopup.padding * 2;
|
||||||
|
|
||||||
|
return Math.max(Math.min(filterContentHeight, maxHeight), minHeight);
|
||||||
|
}
|
||||||
|
padding: Kirigami.Units.smallSpacing
|
||||||
|
modal: false
|
||||||
|
onClosed: userListSearchField.text = ""
|
||||||
|
|
||||||
|
background: Kirigami.ShadowedRectangle {
|
||||||
|
radius: 4
|
||||||
|
color: Kirigami.Theme.backgroundColor
|
||||||
|
|
||||||
|
property color borderColor: Kirigami.Theme.textColor
|
||||||
|
border.color: Qt.rgba(borderColor.r, borderColor.g, borderColor.b, 0.3)
|
||||||
|
border.width: 1
|
||||||
|
|
||||||
|
shadow.xOffset: 0
|
||||||
|
shadow.yOffset: 4
|
||||||
|
shadow.color: Qt.rgba(0, 0, 0, 0.3)
|
||||||
|
shadow.size: 8
|
||||||
|
}
|
||||||
|
|
||||||
|
contentItem: QQC2.ScrollView {
|
||||||
|
// HACK: Hide unnecessary horizontal scrollbar (https://bugreports.qt.io/browse/QTBUG-83890)
|
||||||
|
QQC2.ScrollBar.horizontal.policy: QQC2.ScrollBar.AlwaysOff
|
||||||
|
|
||||||
|
ListView {
|
||||||
|
id: userListView
|
||||||
|
clip: true
|
||||||
|
|
||||||
|
model: UserFilterModel {
|
||||||
|
id: userListFilterModel
|
||||||
|
sourceModel: userListModel
|
||||||
|
filterText: userListSearchField.text
|
||||||
|
|
||||||
|
onFilterTextChanged: {
|
||||||
|
if (filterText.length > 0 && !userListSearchPopup.visible) {
|
||||||
|
userListSearchPopup.open()
|
||||||
|
} else if (filterText.length <= 0 && userListSearchPopup.visible) {
|
||||||
|
userListSearchPopup.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
delegate: Kirigami.BasicListItem {
|
||||||
|
id: userListItem
|
||||||
|
|
||||||
|
implicitHeight: Kirigami.Units.gridUnit * 2
|
||||||
|
leftPadding: Kirigami.Units.largeSpacing + Kirigami.Units.smallSpacing
|
||||||
|
|
||||||
|
label: name
|
||||||
|
labelItem.textFormat: Text.PlainText
|
||||||
|
subtitle: userId
|
||||||
|
subtitleItem.textFormat: Text.PlainText
|
||||||
|
|
||||||
|
action: Kirigami.Action {
|
||||||
|
id: editPowerLevelAction
|
||||||
|
onTriggered: {
|
||||||
|
userListSearchPopup.close()
|
||||||
|
powerLevelSheet.userId = userId
|
||||||
|
powerLevelSheet.powerLevel = powerLevel
|
||||||
|
powerLevelSheet.open()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
leading: Kirigami.Avatar {
|
||||||
|
implicitWidth: height
|
||||||
|
sourceSize.height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
|
sourceSize.width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
|
||||||
|
source: avatar ? ("image://mxc/" + avatar) : ""
|
||||||
|
name: model.userId
|
||||||
|
}
|
||||||
|
|
||||||
|
trailing: QQC2.Label {
|
||||||
|
visible: perm != UserType.Member
|
||||||
|
|
||||||
|
text: {
|
||||||
|
switch (perm) {
|
||||||
|
case UserType.Owner:
|
||||||
|
return i18n("Owner");
|
||||||
|
case UserType.Admin:
|
||||||
|
return i18n("Admin");
|
||||||
|
case UserType.Moderator:
|
||||||
|
return i18n("Mod");
|
||||||
|
case UserType.Muted:
|
||||||
|
return i18n("Muted");
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
color: Kirigami.Theme.disabledTextColor
|
||||||
|
textFormat: Text.PlainText
|
||||||
|
wrapMode: Text.NoWrap
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MobileForm.FormCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
|
visible: room.canSendState("m.room.power_levels")
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
MobileForm.FormCardHeader {
|
||||||
|
title: i18n("Default permissions")
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Default user power level")
|
||||||
|
description: i18n("This is power level for all new users when joining the room")
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.defaultUserPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.defaultUserPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Default power level to set the room state")
|
||||||
|
description: i18n("This is used for all state events that do not have their own entry here")
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.statePowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.statePowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Default power level to send messages")
|
||||||
|
description: i18n("This is used for all message events that do not have their own entry here")
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.defaultEventPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.defaultEventPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MobileForm.FormCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
|
visible: room.canSendState("m.room.power_levels")
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
MobileForm.FormCardHeader {
|
||||||
|
title: i18n("Basic permissions")
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Invite users")
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.invitePowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.invitePowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Kick users")
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.kickPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.kickPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Ban users")
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.banPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.banPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Remove message sent by other users")
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.redactPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.redactPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MobileForm.FormCard {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
|
visible: room.canSendState("m.room.power_levels")
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
MobileForm.FormCardHeader {
|
||||||
|
title: i18n("Event permissions")
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Change user permissions")
|
||||||
|
description: "m.room.power_levels"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.powerLevelPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.powerLevelPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Change the room name")
|
||||||
|
description: "m.room.name"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.namePowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.namePowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Change the room avatar")
|
||||||
|
description: "m.room.avatar"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.avatarPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.avatarPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Change the room canonical alias")
|
||||||
|
description: "m.room.canonical_alias"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.canonicalAliasPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.canonicalAliasPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Change the room topic")
|
||||||
|
description: "m.room.topic"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.topicPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.topicPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Enable encryption for the room")
|
||||||
|
description: "m.room.encryption"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.encryptionPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.encryptionPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Change the room history visibility")
|
||||||
|
description: "m.room.history_visibility"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.historyVisibilityPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.historyVisibilityPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Set pinned events")
|
||||||
|
description: "m.room.pinned_events"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.pinnedEventsPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.pinnedEventsPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Upgrade the room")
|
||||||
|
description: "m.room.tombstone"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.tombstonePowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.tombstonePowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Set the room server access control list (ACL)")
|
||||||
|
description: "m.room.server_acl"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.serverAclPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.serverAclPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
visible: room.isSpace
|
||||||
|
text: i18n("Set the children of this space")
|
||||||
|
description: "m.space.child"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.spaceChildPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.spaceChildPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
MobileForm.FormComboBoxDelegate {
|
||||||
|
text: i18n("Set the parent space of this room")
|
||||||
|
description: "m.space.parent"
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
model: powerLevelModel
|
||||||
|
Component.onCompleted: currentIndex = indexOfValue(room.spaceChildPowerLevel)
|
||||||
|
onCurrentValueChanged: if(room.canSendState("m.room.power_levels")) room.spaceParentPowerLevel = currentValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.OverlaySheet {
|
||||||
|
id: powerLevelSheet
|
||||||
|
title: i18n("Edit user power level")
|
||||||
|
|
||||||
|
property var userId
|
||||||
|
property int powerLevel
|
||||||
|
|
||||||
|
onSheetOpenChanged: {
|
||||||
|
if (sheetOpen) {
|
||||||
|
powerLevelComboBox.currentIndex = powerLevelComboBox.indexOfValue(powerLevelSheet.powerLevel)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kirigami.FormLayout {
|
||||||
|
QQC2.ComboBox {
|
||||||
|
id: powerLevelComboBox
|
||||||
|
focusPolicy: Qt.NoFocus // provided by parent
|
||||||
|
model: powerLevelModel
|
||||||
|
textRole: "text"
|
||||||
|
valueRole: "powerLevel"
|
||||||
|
visible: room.canSendState("m.room.power_levels")
|
||||||
|
}
|
||||||
|
QQC2.Button {
|
||||||
|
text: i18n("Confirm")
|
||||||
|
onClicked: {
|
||||||
|
room.setUserPowerLevel(powerLevelSheet.userId, powerLevelComboBox.currentValue)
|
||||||
|
powerLevelSheet.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ Kirigami.ScrollablePage {
|
|||||||
id: root
|
id: root
|
||||||
|
|
||||||
property var room
|
property var room
|
||||||
|
property string needUpgradeRoom: i18n("You need to upgrade this room to a newer version to enable this setting.")
|
||||||
|
|
||||||
title: i18n("Security")
|
title: i18n("Security")
|
||||||
|
|
||||||
@@ -21,6 +22,29 @@ Kirigami.ScrollablePage {
|
|||||||
rightPadding: 0
|
rightPadding: 0
|
||||||
|
|
||||||
ColumnLayout {
|
ColumnLayout {
|
||||||
|
MobileForm.FormCard {
|
||||||
|
visible: Controller.encryptionSupported
|
||||||
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
|
Layout.fillWidth: true
|
||||||
|
contentItem: ColumnLayout {
|
||||||
|
spacing: 0
|
||||||
|
MobileForm.FormCardHeader {
|
||||||
|
title: i18nc("@option:check", "Encryption")
|
||||||
|
}
|
||||||
|
MobileForm.FormSwitchDelegate {
|
||||||
|
id: enableEncryptionSwitch
|
||||||
|
text: i18n("Enable encryption")
|
||||||
|
description: i18nc("option:check", "Once enabled, encryption cannot be disabled.")
|
||||||
|
enabled: room.canEncryptRoom
|
||||||
|
checked: room.usesEncryption
|
||||||
|
onToggled: if (checked) {
|
||||||
|
let dialog = confirmEncryptionDialog.createObject(applicationWindow(), {room: room});
|
||||||
|
dialog.open();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
MobileForm.FormCard {
|
MobileForm.FormCard {
|
||||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
@@ -40,16 +64,25 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
MobileForm.FormRadioDelegate {
|
MobileForm.FormRadioDelegate {
|
||||||
text: i18nc("@option:check", "Space members")
|
text: i18nc("@option:check", "Space members")
|
||||||
description: !["8", "9", "10"].includes(room.version)
|
description: i18n("Anyone in a space can find and join.") +
|
||||||
? i18n("Anyone in a space can find and join.") + '\n' +
|
(!["8", "9", "10"].includes(room.version) ? `\n${needUpgradeRoom}` : "")
|
||||||
i18n("You need to upgrade this room to a newer version to enable this setting.")
|
|
||||||
: i18n("Anyone in a space can find and join.")
|
|
||||||
checked: room.joinRule === "restricted"
|
checked: room.joinRule === "restricted"
|
||||||
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version) && false
|
enabled: room.canSendState("m.room.join_rules") && ["8", "9", "10"].includes(room.version) && false
|
||||||
onCheckedChanged: if (checked) {
|
onCheckedChanged: if (checked) {
|
||||||
room.joinRule = "restricted";
|
room.joinRule = "restricted";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
MobileForm.FormRadioDelegate {
|
||||||
|
text: i18nc("@option:check", "Knock")
|
||||||
|
description: i18n("People not in the room need to request an invite to join the room.") +
|
||||||
|
(!["7", "8", "9", "10"].includes(room.version) ? `\n${needUpgradeRoom}` : "")
|
||||||
|
checked: room.joinRule === "knock"
|
||||||
|
// https://spec.matrix.org/v1.4/rooms/#feature-matrix
|
||||||
|
enabled: room.canSendState("m.room.join_rules") && ["7", "8", "9", "10"].includes(room.version)
|
||||||
|
onCheckedChanged: if (checked) {
|
||||||
|
room.joinRule = "knock";
|
||||||
|
}
|
||||||
|
}
|
||||||
MobileForm.FormRadioDelegate {
|
MobileForm.FormRadioDelegate {
|
||||||
text: i18nc("@option:check", "Public")
|
text: i18nc("@option:check", "Public")
|
||||||
description: i18nc("@option:check", "Anyone can find and join.")
|
description: i18nc("@option:check", "Anyone can find and join.")
|
||||||
@@ -122,5 +155,25 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Component {
|
||||||
|
id: confirmEncryptionDialog
|
||||||
|
|
||||||
|
ConfirmEncryptionDialog {
|
||||||
|
onClosed: {
|
||||||
|
// At the point this is executed, the state in the room is not yet changed.
|
||||||
|
// The value will be updated when room.onEncryption() emitted.
|
||||||
|
// This is in case if user simply closed the dialog.
|
||||||
|
enableEncryptionSwitch.checked = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Connections {
|
||||||
|
target: room
|
||||||
|
onEncryption: {
|
||||||
|
enableEncryptionSwitch.checked = room.usesEncryption
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -200,7 +200,19 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MobileForm.FormDelegateSeparator { below: colorSchemeDelegate.item ; visible: colorSchemeDelegate.visible }
|
MobileForm.FormDelegateSeparator { below: compactRoomListDelegate }
|
||||||
|
|
||||||
|
MobileForm.FormCheckDelegate {
|
||||||
|
id: compactRoomListDelegate
|
||||||
|
text: i18n("Use compact room list")
|
||||||
|
checked: Config.compactRoomList
|
||||||
|
onToggled: {
|
||||||
|
Config.compactRoomList = checked;
|
||||||
|
Config.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
MobileForm.FormDelegateSeparator { above: compactRoomListDelegate ; below: colorSchemeDelegate.item ; visible: colorSchemeDelegate.visible }
|
||||||
|
|
||||||
Loader {
|
Loader {
|
||||||
id: colorSchemeDelegate
|
id: colorSchemeDelegate
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
MobileForm.AbstractFormDelegate {
|
MobileForm.AbstractFormDelegate {
|
||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
visible: deviceRepeater.count === 0 // We can assume 0 means loading since there is at least one device
|
visible: Controller.activeConnection && deviceRepeater.count === 0 // We can assume 0 means loading since there is at least one device
|
||||||
contentItem: Kirigami.LoadingPlaceholder { }
|
contentItem: Kirigami.LoadingPlaceholder { }
|
||||||
}
|
}
|
||||||
Repeater {
|
Repeater {
|
||||||
@@ -160,6 +160,14 @@ Kirigami.ScrollablePage {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Kirigami.InlineMessage {
|
||||||
|
Layout.fillWidth: true
|
||||||
|
Layout.maximumWidth: Kirigami.Units.gridUnit * 30
|
||||||
|
Layout.alignment: Qt.AlignHCenter
|
||||||
|
text: i18n("Please login to view the signed-in devices for your account.")
|
||||||
|
type: Kirigami.MessageType.Information
|
||||||
|
visible: !Controller.activeConnection
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Kirigami.OverlaySheet {
|
Kirigami.OverlaySheet {
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ Kirigami.ScrollablePage {
|
|||||||
contentItem: MobileForm.FormCheckDelegate {
|
contentItem: MobileForm.FormCheckDelegate {
|
||||||
text: i18n("Enable notifications for this account")
|
text: i18n("Enable notifications for this account")
|
||||||
checked: Config.showNotifications
|
checked: Config.showNotifications
|
||||||
enabled: !Config.isShowNotificationsImmutable
|
enabled: !Config.isShowNotificationsImmutable && Controller.activeConnection
|
||||||
onToggled: {
|
onToggled: {
|
||||||
Config.showNotifications = checked
|
Config.showNotifications = checked
|
||||||
Config.save()
|
Config.save()
|
||||||
@@ -225,6 +225,7 @@ Kirigami.ScrollablePage {
|
|||||||
Layout.fillWidth: true
|
Layout.fillWidth: true
|
||||||
|
|
||||||
placeholderText: i18n("Keyword…")
|
placeholderText: i18n("Keyword…")
|
||||||
|
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
|
||||||
|
|
||||||
rightActions: Kirigami.Action {
|
rightActions: Kirigami.Action {
|
||||||
icon.name: "edit-clear"
|
icon.name: "edit-clear"
|
||||||
@@ -246,6 +247,7 @@ Kirigami.ScrollablePage {
|
|||||||
Accessible.name: text
|
Accessible.name: text
|
||||||
icon.name: "list-add"
|
icon.name: "list-add"
|
||||||
display: QQC2.AbstractButton.IconOnly
|
display: QQC2.AbstractButton.IconOnly
|
||||||
|
enabled: NotificationsManager.keywordNotificationAction !== PushNotificationAction.Unknown
|
||||||
|
|
||||||
onClicked: {
|
onClicked: {
|
||||||
keywordNotificationRuleModel.addKeyword(keywordAddField.text, PushNotificationAction.On)
|
keywordNotificationRuleModel.addKeyword(keywordAddField.text, PushNotificationAction.On)
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user