Compare commits
87 Commits
work/carl/
...
v23.08.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b131b58a9 | ||
|
|
97b7047a48 | ||
|
|
91b5573016 | ||
|
|
8e9bb8a7e4 | ||
|
|
916755b4ad | ||
|
|
9ce288fe47 | ||
|
|
aa461fe078 | ||
|
|
15d8c39185 | ||
|
|
4a1aa97d7b | ||
|
|
e332179ac4 | ||
|
|
7534f293c5 | ||
|
|
fc60a7c13e | ||
|
|
48b8703156 | ||
|
|
5ca85dc670 | ||
|
|
bf8ea41e6a | ||
|
|
fda4ae8735 | ||
|
|
aa0e9fff15 | ||
|
|
28391b53e5 | ||
|
|
a8d7744b42 | ||
|
|
9fcc858841 | ||
|
|
f7092eee69 | ||
|
|
9a426f941a | ||
|
|
c77dee09f5 | ||
|
|
26dde017d0 | ||
|
|
5b27a84bcc | ||
|
|
fc3c36838c | ||
|
|
1fd2072cf8 | ||
|
|
7770d412b3 | ||
|
|
56184920b4 | ||
|
|
b61685b151 | ||
|
|
be0212a686 | ||
|
|
37ed09f957 | ||
|
|
1b7e8ae77a | ||
|
|
7571b0a9a3 | ||
|
|
9477e6343f | ||
|
|
83241ea956 | ||
|
|
f054467878 | ||
|
|
fb7154b492 | ||
|
|
973a75128c | ||
|
|
9ea70d7eac | ||
|
|
7f8285037c | ||
|
|
c5f19f12e5 | ||
|
|
ed6379ff85 | ||
|
|
9750df90e0 | ||
|
|
bd90658668 | ||
|
|
f78b634720 | ||
|
|
250476f77c | ||
|
|
e293d1f644 | ||
|
|
cde50588cb | ||
|
|
3972dab7b9 | ||
|
|
7c1b43bcad | ||
|
|
c22c74f735 | ||
|
|
9c42b3c1a7 | ||
|
|
d45cd5d9c9 | ||
|
|
8c503b8258 | ||
|
|
7078caac15 | ||
|
|
94bf65ab43 | ||
|
|
244e93c926 | ||
|
|
412856e882 | ||
|
|
2421f11717 | ||
|
|
2d42cfedec | ||
|
|
9308c84696 | ||
|
|
72fc242364 | ||
|
|
7d3240c597 | ||
|
|
9906ba8b7d | ||
|
|
b949092bc6 | ||
|
|
5cce3bd692 | ||
|
|
ef5d67e5bf | ||
|
|
4711b954c5 | ||
|
|
1688b00f48 | ||
|
|
410befa4bf | ||
|
|
b380c74b0e | ||
|
|
da8227931f | ||
|
|
fdc45ec5c8 | ||
|
|
94dd69ea02 | ||
|
|
80c3253a6e | ||
|
|
c5f6a6607e | ||
|
|
a10d5cbe75 | ||
|
|
584cad19b1 | ||
|
|
3745414635 | ||
|
|
147ce42c04 | ||
|
|
928feeca82 | ||
|
|
fc04af536d | ||
|
|
12b949d6cb | ||
|
|
6aaaabf159 | ||
|
|
fe45ef85a6 | ||
|
|
13e38ed982 |
@@ -57,11 +57,6 @@ Dependencies:
|
||||
- 'on': ['Linux/Qt6', 'FreeBSD/Qt6']
|
||||
'require':
|
||||
'frameworks/kdbusaddons': '@latest-kf6'
|
||||
'frameworks/kstatusnotifieritem': '@latest-kf6'
|
||||
|
||||
- 'on': ['Linux/Qt6', 'Linux/Qt5']
|
||||
'require':
|
||||
'sdk/selenium-webdriver-at-spi': '@latest-kf6'
|
||||
|
||||
Options:
|
||||
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD', 'Windows' ]
|
||||
require-passing-tests-on: [ 'Linux/Qt5', 'FreeBSD' ]
|
||||
|
||||
@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "23")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "11")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "70")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "08")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "0")
|
||||
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
|
||||
|
||||
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
|
||||
@@ -27,7 +27,7 @@ set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${CMAKE_SOURCE_DIR}/cmake)
|
||||
set(CMAKE_CXX_STANDARD 20)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
set(KDE_COMPILERSETTINGS_LEVEL 5.105)
|
||||
set(KDE_COMPILERSETTINGS_LEVEL 5.84)
|
||||
|
||||
include(FeatureSummary)
|
||||
include(ECMSetupVersion)
|
||||
@@ -93,9 +93,11 @@ set_package_properties(KF${QT_MAJOR_VERSION}Kirigami2 PROPERTIES
|
||||
)
|
||||
find_package(KF${QT_MAJOR_VERSION}KirigamiAddons 0.7.2 REQUIRED)
|
||||
|
||||
if(QT_MAJOR_VERSION STREQUAL "6")
|
||||
find_package(KF6StatusNotifierItem ${KF_MIN_VERSION} REQUIRED)
|
||||
endif()
|
||||
find_package(Qt${QT_MAJOR_VERSION}Keychain)
|
||||
set_package_properties(Qt${QT_MAJOR_VERSION}Keychain PROPERTIES
|
||||
TYPE REQUIRED
|
||||
PURPOSE "Secure storage of account secrets"
|
||||
)
|
||||
|
||||
if(ANDROID)
|
||||
find_package(OpenSSL)
|
||||
@@ -128,11 +130,6 @@ set_package_properties(Quotient${QUOTIENT_SUFFIX} PROPERTIES
|
||||
PURPOSE "Talk with matrix server"
|
||||
)
|
||||
|
||||
if (NOT TARGET Olm::Olm)
|
||||
message(FATAL_ERROR "NeoChat requires Quotient with the E2EE feature enabled")
|
||||
endif()
|
||||
|
||||
|
||||
find_package(cmark)
|
||||
set_package_properties(cmark PROPERTIES
|
||||
TYPE REQUIRED
|
||||
@@ -163,6 +160,10 @@ set_package_properties(KF${QT_MAJOR_VERSION}DocTools PROPERTIES DESCRIPTION
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
|
||||
if(NOT Quotient${QUOTIENT_SUFFIX}_VERSION_MINOR GREATER 6)
|
||||
cmake_policy(SET CMP0063 OLD)
|
||||
endif()
|
||||
|
||||
if(ANDROID)
|
||||
find_package(Sqlite3)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/android/version.gradle.in ${CMAKE_BINARY_DIR}/version.gradle)
|
||||
@@ -178,11 +179,9 @@ install(FILES org.kde.neochat.tray.svg DESTINATION ${KDE_INSTALL_FULL_ICONDIR}/h
|
||||
add_definitions(-DQT_NO_FOREACH)
|
||||
|
||||
add_subdirectory(src)
|
||||
|
||||
if (BUILD_TESTING)
|
||||
if (BUILD_TESTING AND Quotient${QUOTIENT_SUFFIX}_VERSION_MINOR GREATER 6)
|
||||
find_package(Qt${QT_MAJOR_VERSION} ${QT_MIN_VERSION} NO_MODULE COMPONENTS Test)
|
||||
add_subdirectory(autotests)
|
||||
add_subdirectory(appiumtests)
|
||||
endif()
|
||||
|
||||
if(KF${QT_MAJOR_VERSION}DocTools_FOUND)
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
|
||||
A Qt/QML based Matrix client.
|
||||
|
||||
<a href='https://matrix.org'><img src='https://matrix.org/docs/legacy/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
|
||||
<a href='https://matrix.org'><img src='https://matrix.org/docs/projects/images/made-for-matrix.png' alt='Made for Matrix' height=64 target=_blank /></a>
|
||||
<a href='https://flathub.org/apps/details/org.kde.neochat'><img width='190px' alt='Download on Flathub' src='https://flathub.org/assets/badges/flathub-badge-i-en.png'/></a>
|
||||
|
||||
## Introduction
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
# SPDX-License-Identifier: BSD-3-Clause
|
||||
# SPDX-FileCopyrightText: 2022 Harald Sitter <sitter@kde.org>
|
||||
|
||||
|
||||
if(NOT BUILD_TESTING OR NOT CMAKE_SYSTEM_NAME MATCHES "Linux")
|
||||
return()
|
||||
endif()
|
||||
|
||||
find_package(SeleniumWebDriverATSPI)
|
||||
set_package_properties(SeleniumWebDriverATSPI PROPERTIES
|
||||
DESCRIPTION "Server component for selenium tests using Linux accessibility infrastructure"
|
||||
PURPOSE "Needed for GUI tests"
|
||||
URL "https://invent.kde.org/sdk/selenium-webdriver-at-spi"
|
||||
TYPE OPTIONAL
|
||||
)
|
||||
if(NOT SeleniumWebDriverATSPI_FOUND)
|
||||
return()
|
||||
endif()
|
||||
|
||||
add_test(
|
||||
NAME logintest
|
||||
COMMAND selenium-webdriver-at-spi-run ${CMAKE_CURRENT_SOURCE_DIR}/logintest.py
|
||||
)
|
||||
@@ -1,42 +0,0 @@
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
|
||||
|
||||
from flask import Flask, request, abort
|
||||
app = Flask(__name__)
|
||||
|
||||
@app.route("/_matrix/client/v3/login", methods=["GET"])
|
||||
def login_get():
|
||||
result = dict()
|
||||
result["flows"] = [dict()]
|
||||
result["flows"][0]["type"] = "m.login.password"
|
||||
return result
|
||||
|
||||
@app.route("/_matrix/client/v3/login", methods=["POST"])
|
||||
def login_post():
|
||||
data = request.get_json()
|
||||
if data["identifier"]["user"] != "user" or data["password"] != "1234":
|
||||
abort(403)
|
||||
print(data)
|
||||
result = dict()
|
||||
result["access_token"] = "token_1234"
|
||||
result["device_id"] = "device_1234"
|
||||
result["user_id"] = "@user:localhost:1234"
|
||||
return result
|
||||
|
||||
@app.route("/_matrix/client/r0/sync")
|
||||
def sync():
|
||||
result = dict()
|
||||
result["next_batch"] = "batch1234"
|
||||
return result
|
||||
|
||||
@app.route("/.well-known/matrix/client")
|
||||
def well_known():
|
||||
reply = dict()
|
||||
reply["m.homeserver"] = dict()
|
||||
reply["m.homeserver"]["base_url"] = "https://localhost:1234"
|
||||
return reply
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app.run(ssl_context='adhoc', port=1234)
|
||||
@@ -1,44 +0,0 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# SPDX-License-Identifier: MIT
|
||||
# SPDX-FileCopyrightText: 2021-2022 Harald Sitter <sitter@kde.org>
|
||||
# SPDX-FileCopyrightText: 2023 Tobias Fella <tobias.fella@kde.org>
|
||||
|
||||
import unittest
|
||||
from appium import webdriver
|
||||
from appium.webdriver.common.appiumby import AppiumBy
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
import time
|
||||
import subprocess
|
||||
|
||||
class LoginTest(unittest.TestCase):
|
||||
@classmethod
|
||||
def setUpClass(self):
|
||||
desired_caps = {}
|
||||
desired_caps["app"] = "neochat --ignore-ssl-errors"
|
||||
desired_caps["timeouts"] = {'implicit': 10000}
|
||||
self.driver = webdriver.Remote(
|
||||
command_executor='http://127.0.0.1:4723',
|
||||
desired_capabilities=desired_caps)
|
||||
subprocess.Popen(["python","login-server.py"])
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
|
||||
def tearDown(self):
|
||||
if not self._outcome.result.wasSuccessful():
|
||||
self.driver.get_screenshot_as_file("failed_test_shot_{}.png".format(self.id()))
|
||||
|
||||
@classmethod
|
||||
def tearDownClass(self):
|
||||
self.driver.quit()
|
||||
|
||||
def test_login(self):
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Matrix ID").send_keys("@user:localhost:1234")
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Continue").click()
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Password").send_keys("1234")
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Login").click()
|
||||
self.driver.find_element(by=AppiumBy.NAME, value="Join some rooms to get started").click()
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
@@ -10,8 +10,6 @@
|
||||
#include <Quotient/syncdata.h>
|
||||
#include <qnamespace.h>
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
class TestRoom : public NeoChatRoom
|
||||
@@ -557,10 +555,10 @@ void TextHandlerTest::receiveRichPlainUrl()
|
||||
void TextHandlerTest::receiveRichEmote()
|
||||
{
|
||||
auto event = room->messageEvents().at(1).get();
|
||||
auto author = room->user(event->senderId());
|
||||
auto author = static_cast<NeoChatUser *>(room->user(event->senderId()));
|
||||
const QString testInputString = QStringLiteral("This is an emote.");
|
||||
const QString testOutputString = QStringLiteral("* <a href=\"https://matrix.to/#/@example:example.org\" style=\"color:")
|
||||
+ Utils::getUserColor(author->hueF()).name() + QStringLiteral("\">@example:example.org</a> This is an emote.");
|
||||
const QString testOutputString = QStringLiteral("* <a href=\"https://matrix.to/#/@example:example.org\" style=\"color:") + author->color().name()
|
||||
+ QStringLiteral("\">@example:example.org</a> This is an emote.");
|
||||
|
||||
TextHandler testTextHandler;
|
||||
testTextHandler.setData(testInputString);
|
||||
@@ -604,13 +602,13 @@ void TextHandlerTest::linkPreviewsMatch_data()
|
||||
QTest::addColumn<QString>("testInputString");
|
||||
QTest::addColumn<QList<QUrl>>("testOutputLinks");
|
||||
|
||||
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org"_ls)});
|
||||
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org"_ls)});
|
||||
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org"_ls)});
|
||||
QTest::newRow("plainHttps") << QStringLiteral("https://kde.org") << QList<QUrl>({QUrl("https://kde.org")});
|
||||
QTest::newRow("richHttps") << QStringLiteral("<a href=\"https://kde.org\">Rich Link</a>") << QList<QUrl>({QUrl("https://kde.org")});
|
||||
QTest::newRow("plainWww") << QStringLiteral("www.example.org") << QList<QUrl>({QUrl("www.example.org")});
|
||||
QTest::newRow("multipleHttps") << QStringLiteral("https://kde.org www.example.org")
|
||||
<< QList<QUrl>({
|
||||
QUrl("https://kde.org"_ls),
|
||||
QUrl("www.example.org"_ls),
|
||||
QUrl("https://kde.org"),
|
||||
QUrl("www.example.org"),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -646,5 +644,5 @@ void TextHandlerTest::linkPreviewsReject()
|
||||
QCOMPARE(testTextHandler.getLinkPreviews(), testOutputLinks);
|
||||
}
|
||||
|
||||
QTEST_MAIN(TextHandlerTest)
|
||||
QTEST_GUILESS_MAIN(TextHandlerTest)
|
||||
#include "texthandlertest.moc"
|
||||
|
||||
@@ -46,25 +46,43 @@
|
||||
<name xml:lang="uk">NeoChat</name>
|
||||
<name xml:lang="x-test">xxNeoChatxx</name>
|
||||
<name xml:lang="zh-CN">NeoChat</name>
|
||||
<summary>Chat with your friends on matrix</summary>
|
||||
<summary xml:lang="ar">دردش مع أصدقائك على ماتركس</summary>
|
||||
<summary xml:lang="ca">Xategeu amb els vostres amics a Matrix</summary>
|
||||
<summary xml:lang="ca-valencia">Xategeu amb els vostres amics a Matrix</summary>
|
||||
<summary xml:lang="eo">Babilu kun viaj amikoj sur matrix</summary>
|
||||
<summary xml:lang="es">Charle con sus amigos en matrix</summary>
|
||||
<summary xml:lang="eu">Berriketan jardun zure lagunekin «Matrix»en</summary>
|
||||
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
|
||||
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
||||
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
|
||||
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
|
||||
<summary xml:lang="nl">Met uw vrienden chatten op matrix</summary>
|
||||
<summary xml:lang="nn">Prat med vennar på Matrix</summary>
|
||||
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
|
||||
<summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
|
||||
<summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
|
||||
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
|
||||
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
|
||||
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
|
||||
<summary>A client for matrix, the decentralized communication protocol</summary>
|
||||
<summary xml:lang="ar">عميل لماتركس، ميفاق الاتصال اللامركزي</summary>
|
||||
<summary xml:lang="az">Matrix üçün müştəri, mərkəzləşməmiş kommunikasiya protokolu</summary>
|
||||
<summary xml:lang="ca">Un client de Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="ca-valencia">Un client de Matrix, el protocol de comunicacions descentralitzat</summary>
|
||||
<summary xml:lang="cs">Klient pro decentralizovaný komunikační protokol matrix</summary>
|
||||
<summary xml:lang="de">Ein Programm für Matrix, das dezentrale Kommunikationsprotokoll</summary>
|
||||
<summary xml:lang="el">Ένας πελάτης για το Matrix, το αποκεντρωμένο πρωτόκολλο επικοινωνίας</summary>
|
||||
<summary xml:lang="en-GB">A client for matrix, the decentralised communication protocol</summary>
|
||||
<summary xml:lang="eo">Kliento por matrix, la malcentra komunikprotokolo</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="fi">Asiakas Matrixille, hajautetulle viestintäyhteyskäytännölle</summary>
|
||||
<summary xml:lang="fr">Un client pour « Matrix », le protocole décentralisé de communications.</summary>
|
||||
<summary xml:lang="gl">Un cliente para Matrix, o protocolo de comunicación descentralizada.</summary>
|
||||
<summary xml:lang="hu">Kliens a matrixhoz, a decentralizált kommunikációs protokollhoz</summary>
|
||||
<summary xml:lang="ia">Un cliente per Matrix, le protocollo de communication decentralisate</summary>
|
||||
<summary xml:lang="id">Klien untuk matrix, protokol komunikasi terdesentralisasi</summary>
|
||||
<summary xml:lang="ie">Un cliente de Matrix, li protocol de communication decentralisat</summary>
|
||||
<summary xml:lang="it">Un client per matrix, il protocollo di comunicazione decentralizzato</summary>
|
||||
<summary xml:lang="ka">კლიენტი Matrix-სთვის, დეცენტრალიზებული კომუნიკაციის პროტოკოლისთვის</summary>
|
||||
<summary xml:lang="ko">Matrix, 분산 대화 프로토콜 클라이언트</summary>
|
||||
<summary xml:lang="nl">Een client voor matrix, het gedecentraliseerde communicatieprotocol</summary>
|
||||
<summary xml:lang="nn">Ein klient for Matrix – protokollen for desentralisert kommunikasjon</summary>
|
||||
<summary xml:lang="pa">ਮੈਟਰਿਕਸ, ਸਰਬ-ਸਾਂਝੇ ਸੰਚਾਰ ਪਰੋਟੋਕਾਲ, ਲਈ ਕਲਾਈਂਟ ਹੈ</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-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="sl">Odjemalec za matrix, decentralizirani komunikacijski protokol</summary>
|
||||
<summary xml:lang="sv">En klient för Matrix, det decentraliserade kommunikationsprotokollet</summary>
|
||||
<summary xml:lang="ta">மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி</summary>
|
||||
<summary xml:lang="tr">Merkezi olmayan iletişim protokolü Matrix için bir istemci</summary>
|
||||
<summary xml:lang="uk">Клієнт matrix, децентралізованого протоколу обміну даними</summary>
|
||||
<summary xml:lang="x-test">xxA client for matrix, the decentralized communication protocolxx</summary>
|
||||
<summary xml:lang="zh-CN">分布式通讯协议 Matrix 的客户端</summary>
|
||||
<description>
|
||||
<p>NeoChat is a client for Matrix, the decentralized communication protocol for instant messaging. It allows you to send text messages, videos and audio files to your family, colleagues and friends. It uses KDE frameworks and most notably Kirigami
|
||||
to provide a convergent experience across multiple platforms.</p>
|
||||
@@ -96,7 +114,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="ca">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de la VoIP, fils i alguns aspectes de l'encriptatge d'extrem a extrem. Hi ha algunes altres omissions més petites a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu segueix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="ca-valencia">NeoChat pretén ser una aplicació amb totes les característiques per a l'especificació de Matrix. Com a tal, s'ha implementat tota l'especificació actual estable amb les notables excepcions de VoIP, fils i alguns aspectes de l'encriptació d'extrem a extrem. Hi ha algunes altres omissions més xicotetes a causa del fet que l'especificació de Matrix està evolucionant constantment, però l'objectiu seguix sent proporcionar suport eventual per a tota l'especificació.</p>
|
||||
<p xml:lang="en-GB">NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
|
||||
<p xml:lang="eo">NeoChat celas esti plene kapabla aplikaĵo por la Matrix-specifo. Kiel tia, ĉio en la nuna stabila specifo kun la rimarkindaj esceptoj de VoIP, fadenoj kaj kelkaj aspektoj de Fin-al-Fina Ĉifrado estas subtenataj. Estas kelkaj aliaj pli malgrandaj preterlasoj pro la fakto, ke la Matrix-speco konstante evoluas, sed la celo restas provizi finfine subtenon por la tuta specifaĵo.</p>
|
||||
<p xml:lang="es">NeoChat pretende ser una aplicación con todas las funciones para la especificación de Matrix. Como tal, admite todo en la especificación estable actual, con las notables excepciones de VoIP, subprocesos y algunas funciones de cifrado de extremo a extremo. Existen algunas omisiones menos importantes debido al hecho de que la especificación de Matrix está en constante evolución, pero el objetivo sigue siendo brindar compatibilidad final con toda la especificación.</p>
|
||||
<p xml:lang="eu">«NeoChat»ek «Matrix» zehaztapenaren ezaugarri guztiak eskaintzen dituen aplikazio bat izan nahi du. Beraz, egungo zehaztapen egonkorrean dagoen guztiaren euskarria du, VoIP, hariak eta muturren artean zifratzeko salbuespen nabarmenekin. Badira beste ez-betetze txikiago batzuk, «Matrix»en zehaztapena etengabe eboluzioan dagoelako, baina azken helburua zehaztapen osoaren euskarria ematea izaten jarraitzen du.</p>
|
||||
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
|
||||
@@ -120,7 +137,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="ca">A causa de la naturalesa del desenvolupament de l'especificació de Matrix, el NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="ca-valencia">A causa de la naturalea del desenvolupament de l'especificació de Matrix, NeoChat també implementa nombroses característiques inestables. Actualment són:</p>
|
||||
<p xml:lang="en-GB">Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
|
||||
<p xml:lang="eo">Pro la naturo de la Matrix-specifevoluo NeoChat ankaŭ subtenas multajn malstabilajn funkciojn. Nuntempe ĉi tiuj estas:</p>
|
||||
<p xml:lang="es">Debido a la naturaleza del desarrollo de la especificación de Matrix, NeoChat también permite numerosas funciones no estables, como:</p>
|
||||
<p xml:lang="eu">«Matrix» zehaztapenaren garapenaren izaeragatik, «Neo¢hat»ek ezaugarri ezegonkor ugari ere onartzen ditu. Gaur egun hauek:</p>
|
||||
<p xml:lang="fi">Matrix-määritelmän kehittyessä NeoChat tukee myös monia epävakaita ominaisuuksia. Tällä hetkellä näitä ovat:</p>
|
||||
@@ -146,7 +162,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="ca">Enquestes - MSC3381</li>
|
||||
<li xml:lang="ca-valencia">Enquestes - MSC3381</li>
|
||||
<li xml:lang="en-GB">Polls - MSC3381</li>
|
||||
<li xml:lang="eo">Enketoj - MSC3381</li>
|
||||
<li xml:lang="es">Encuestas - MSC3381</li>
|
||||
<li xml:lang="eu">Polls - MSC3381</li>
|
||||
<li xml:lang="fi">Kyselyt – MSC3381</li>
|
||||
@@ -170,7 +185,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="ca">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="ca-valencia">Paquets d'adhesius - MSC2545</li>
|
||||
<li xml:lang="en-GB">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="eo">Glumark-Pakoj - MSC2545</li>
|
||||
<li xml:lang="es">Paquetes de pegatinas - MSC2545</li>
|
||||
<li xml:lang="eu">Eranskailu paketeak - MSC2545</li>
|
||||
<li xml:lang="fi">Tarrapakkaukset – MSC2545</li>
|
||||
@@ -194,7 +208,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="ca">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="ca-valencia">Esdeveniments d'ubicació - MSC3488</li>
|
||||
<li xml:lang="en-GB">Location Events - MSC3488</li>
|
||||
<li xml:lang="eo">Lokaj Eventoj - MSC3488</li>
|
||||
<li xml:lang="es">Eventos de ubicación - MSC3488</li>
|
||||
<li xml:lang="eu">Kokaleku-gertaerak - MSC3488</li>
|
||||
<li xml:lang="fi">Sijaintitapahtumat – MSC3488</li>
|
||||
@@ -269,15 +282,14 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
|
||||
<value key="KDE::windows_store::screenshots::1::image">https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Timeline.png</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption">Main view with room list, chat, and room information</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ar">العرض الرئيسة مع قائمة الغرف والدردشات و معلومات الغرفة</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ca">Vista principal amb la llista de sales, xats i informació de les sales</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ca-valencia">Vista principal amb la llista de sales, xats i informació de les sales</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="eo">Ĉefa vido kun ĉambra listo, babilejo kaj ĉambra informo</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="es">Vista principal con la lista de salas, chat e información de la sala</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="eu">Ikuspegi nagusia gela-zerrenda, berriketa, eta gelako informazioarekin</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ia">Vista principal con lista de sala, chat e information de sala</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</value>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="ko">대화방 목록, 채팅, 대화방 정보가 표시된 주 보기</value>
|
||||
@@ -292,15 +304,14 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<value key="KDE::windows_store::screenshots::1::caption" xml:lang="x-test">xxMain view with room list, chat, and room informationxx</value>
|
||||
<value key="KDE::windows_store::screenshots::2::image">https://cdn.kde.org/screenshots/neochat/NeoChat-Windows-Login.png</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption">Login screen</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ar">شاشة الدخول</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ca">Pantalla d'inici de sessió</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ca-valencia">Pantalla d'inici de sessió</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="eo">Ensaluta ekrano</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="es">Pantalla de inicio de sesión</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="eu">Saio-hasteko pantaila</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="fi">Kirjautumisnäkymä</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="fr">Écran de connexion</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="gl">Pantalla de identificación.</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ia">Schermo de accesso</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="it">Schermata di accesso</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ka">შესვლის ეკრანი</value>
|
||||
<value key="KDE::windows_store::screenshots::2::caption" xml:lang="ko">로그인 화면</value>
|
||||
|
||||
713
po/ar/neochat.po
713
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
678
po/az/neochat.po
678
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
680
po/ca/neochat.po
680
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
673
po/cs/neochat.po
673
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
678
po/da/neochat.po
678
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
674
po/de/neochat.po
674
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
674
po/el/neochat.po
674
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
681
po/es/neochat.po
681
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
679
po/eu/neochat.po
679
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
682
po/fi/neochat.po
682
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
720
po/fr/neochat.po
720
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
683
po/hu/neochat.po
683
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
679
po/ia/neochat.po
679
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
683
po/id/neochat.po
683
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
674
po/ie/neochat.po
674
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
679
po/it/neochat.po
679
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
675
po/ja/neochat.po
675
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
680
po/ka/neochat.po
680
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
673
po/ko/neochat.po
673
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
675
po/lt/neochat.po
675
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
682
po/nl/neochat.po
682
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
539
po/nn/neochat.po
539
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
678
po/pa/neochat.po
678
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
683
po/pl/neochat.po
683
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
683
po/pt/neochat.po
683
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
683
po/ru/neochat.po
683
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
678
po/sk/neochat.po
678
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
684
po/sl/neochat.po
684
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
699
po/sv/neochat.po
699
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
680
po/ta/neochat.po
680
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
690
po/tr/neochat.po
690
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
680
po/uk/neochat.po
680
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -5,6 +5,7 @@
|
||||
|
||||
configure_file(qml/Page/RoomList/RoomDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Page/RoomList/RoomDelegate.qml)
|
||||
configure_file(qml/Component/QuickSwitcher.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/QuickSwitcher.qml)
|
||||
configure_file(qml/Dialog/UserDetailDialog.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Dialog/UserDetailDialog.qml)
|
||||
configure_file(qml/Dialog/PowerLevelDialog.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Dialog/PowerLevelDialog.qml)
|
||||
configure_file(qml/Component/Timeline/AudioDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/Timeline/AudioDelegate.qml)
|
||||
configure_file(qml/Component/Timeline/VideoDelegate.qml ${CMAKE_CURRENT_BINARY_DIR}/qml/Component/Timeline/VideoDelegate.qml)
|
||||
@@ -43,6 +44,8 @@ add_library(neochat STATIC
|
||||
roommanager.h
|
||||
neochatroom.cpp
|
||||
neochatroom.h
|
||||
neochatuser.cpp
|
||||
neochatuser.h
|
||||
models/userlistmodel.cpp
|
||||
models/userlistmodel.h
|
||||
models/userfiltermodel.cpp
|
||||
@@ -124,7 +127,6 @@ add_library(neochat STATIC
|
||||
locationhelper.h
|
||||
events/pollevent.cpp
|
||||
pollhandler.cpp
|
||||
utils.h
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(neochat
|
||||
@@ -155,9 +157,6 @@ if(NOT ANDROID)
|
||||
target_sources(neochat PRIVATE colorschemer.cpp colorschemer.h)
|
||||
if (NOT WIN32 AND NOT APPLE)
|
||||
target_sources(neochat PRIVATE trayicon_sni.cpp trayicon_sni.h)
|
||||
if(QT_MAJOR_VERSION STREQUAL "6")
|
||||
target_link_libraries(neochat PRIVATE KF6::StatusNotifierItem)
|
||||
endif()
|
||||
else()
|
||||
target_sources(neochat PRIVATE trayicon.cpp trayicon.h)
|
||||
endif()
|
||||
@@ -176,7 +175,7 @@ else()
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR})
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient${QUOTIENT_SUFFIX} cmark::cmark QCoro::Core)
|
||||
target_link_libraries(neochat PUBLIC Qt::Core Qt::Quick Qt::Qml Qt::Gui Qt::Multimedia Qt::Network Qt::QuickControls2 KF${QT_MAJOR_VERSION}::I18n KF${QT_MAJOR_VERSION}::Kirigami2 KF${QT_MAJOR_VERSION}::Notifications KF${QT_MAJOR_VERSION}::ConfigCore KF${QT_MAJOR_VERSION}::ConfigGui KF${QT_MAJOR_VERSION}::CoreAddons KF${QT_MAJOR_VERSION}::SonnetCore KF${QT_MAJOR_VERSION}::ItemModels Quotient${QUOTIENT_SUFFIX} cmark::cmark ${QTKEYCHAIN_LIBRARIES} QCoro::Core)
|
||||
kconfig_add_kcfg_files(neochat GENERATE_MOC neochatconfig.kcfgc)
|
||||
|
||||
if(NEOCHAT_FLATPAK)
|
||||
|
||||
@@ -44,7 +44,7 @@ void ActionsHandler::handleNewMessage()
|
||||
if (!m_room->chatBoxAttachmentPath().isEmpty()) {
|
||||
QUrl url(m_room->chatBoxAttachmentPath());
|
||||
auto path = url.isLocalFile() ? url.toLocalFile() : url.toString();
|
||||
m_room->uploadFile(QUrl(path), m_room->chatBoxText().isEmpty() ? path.mid(path.lastIndexOf(u'/') + 1) : m_room->chatBoxText());
|
||||
m_room->uploadFile(path, m_room->chatBoxText().isEmpty() ? path.mid(path.lastIndexOf('/') + 1) : m_room->chatBoxText());
|
||||
m_room->setChatBoxAttachmentPath({});
|
||||
m_room->setChatBoxText({});
|
||||
return;
|
||||
@@ -97,7 +97,7 @@ QString ActionsHandler::handleMentions(QString handledText, const bool &isEdit)
|
||||
void ActionsHandler::handleMessage(const QString &text, QString handledText, const bool &isEdit)
|
||||
{
|
||||
if (NeoChatConfig::allowQuickEdit()) {
|
||||
QRegularExpression sed(QStringLiteral("^s/([^/]*)/([^/]*)(/g)?$"));
|
||||
QRegularExpression sed("^s/([^/]*)/([^/]*)(/g)?$");
|
||||
auto match = sed.match(text);
|
||||
if (match.hasMatch()) {
|
||||
const QString regex = match.captured(1);
|
||||
@@ -113,13 +113,13 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, con
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
if (flags == "/g"_ls) {
|
||||
m_room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), {}, event->id());
|
||||
if (flags == "/g") {
|
||||
m_room->postHtmlMessage(handledText, originalString.replace(regex, replacement), event->msgtype(), "", event->id());
|
||||
} else {
|
||||
m_room->postHtmlMessage(handledText,
|
||||
originalString.replace(originalString.indexOf(regex), regex.size(), replacement),
|
||||
event->msgtype(),
|
||||
{},
|
||||
"",
|
||||
event->id());
|
||||
}
|
||||
return;
|
||||
@@ -133,7 +133,7 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, con
|
||||
if (handledText.startsWith(QLatin1Char('/'))) {
|
||||
for (const auto &action : ActionsModel::instance().allActions()) {
|
||||
if (handledText.indexOf(action.prefix) == 1
|
||||
&& (handledText.indexOf(" "_ls) == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
|
||||
&& (handledText.indexOf(" ") == action.prefix.length() + 1 || handledText.length() == action.prefix.length() + 1)) {
|
||||
handledText = action.handle(handledText.mid(action.prefix.length() + 1).trimmed(), m_room);
|
||||
if (action.messageType.has_value()) {
|
||||
messageType = *action.messageType;
|
||||
@@ -152,30 +152,30 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, con
|
||||
textHandler.setData(handledText);
|
||||
handledText = textHandler.handleSendText();
|
||||
|
||||
if (handledText.count("<p>"_ls) == 1 && handledText.count("</p>"_ls) == 1) {
|
||||
handledText.remove("<p>"_ls);
|
||||
handledText.remove("</p>"_ls);
|
||||
if (handledText.count("<p>") == 1 && handledText.count("</p>") == 1) {
|
||||
handledText.remove("<p>");
|
||||
handledText.remove("</p>");
|
||||
}
|
||||
|
||||
if (handledText.length() == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : QString());
|
||||
m_room->postMessage(text, handledText, messageType, m_room->chatBoxReplyId(), isEdit ? m_room->chatBoxEditId() : "");
|
||||
}
|
||||
|
||||
void ActionsHandler::checkEffects(const QString &text)
|
||||
{
|
||||
std::optional<QString> effect = std::nullopt;
|
||||
if (text.contains(QStringLiteral("\u2744"))) {
|
||||
if (text.contains("\u2744")) {
|
||||
effect = QLatin1String("snowflake");
|
||||
} else if (text.contains(QStringLiteral("\u1F386"))) {
|
||||
} else if (text.contains("\u1F386")) {
|
||||
effect = QLatin1String("fireworks");
|
||||
} else if (text.contains(QStringLiteral("\u2F387"))) {
|
||||
} else if (text.contains("\u2F387")) {
|
||||
effect = QLatin1String("fireworks");
|
||||
} else if (text.contains(QStringLiteral("\u1F389"))) {
|
||||
} else if (text.contains("\u1F389")) {
|
||||
effect = QLatin1String("confetti");
|
||||
} else if (text.contains(QStringLiteral("\u1F38A"))) {
|
||||
} else if (text.contains("\u1F38A")) {
|
||||
effect = QLatin1String("confetti");
|
||||
}
|
||||
if (effect.has_value()) {
|
||||
|
||||
@@ -225,7 +225,7 @@ void ChatDocumentHandler::complete(int index)
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
|
||||
cursor.insertText(name + QStringLiteral(" "));
|
||||
cursor.insertText(name % " ");
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursor.position() + name.size(), QTextCursor::KeepAnchor);
|
||||
cursor.setKeepPositionOnInsert(true);
|
||||
@@ -246,7 +246,7 @@ void ChatDocumentHandler::complete(int index)
|
||||
QTextCursor cursor(document()->textDocument());
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursorPosition(), QTextCursor::KeepAnchor);
|
||||
cursor.insertText(alias + QStringLiteral(" "));
|
||||
cursor.insertText(alias % " ");
|
||||
cursor.setPosition(at);
|
||||
cursor.setPosition(cursor.position() + alias.size(), QTextCursor::KeepAnchor);
|
||||
cursor.setKeepPositionOnInsert(true);
|
||||
|
||||
@@ -104,12 +104,12 @@ class ChatDocumentHandler : public QObject
|
||||
/**
|
||||
* @brief The color to highlight user mentions.
|
||||
*/
|
||||
Q_PROPERTY(QColor mentionColor READ mentionColor WRITE setMentionColor NOTIFY mentionColorChanged)
|
||||
Q_PROPERTY(QColor mentionColor READ mentionColor WRITE setMentionColor NOTIFY mentionColorChanged);
|
||||
|
||||
/**
|
||||
* @brief The color to highlight spelling errors.
|
||||
*/
|
||||
Q_PROPERTY(QColor errorColor READ errorColor WRITE setErrorColor NOTIFY errorColorChanged)
|
||||
Q_PROPERTY(QColor errorColor READ errorColor WRITE setErrorColor NOTIFY errorColorChanged);
|
||||
|
||||
public:
|
||||
explicit ChatDocumentHandler(QObject *parent = nullptr);
|
||||
|
||||
@@ -35,15 +35,15 @@
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
#include <Quotient/csapi/logout.h>
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/eventstats.h>
|
||||
#include <Quotient/jobs/downloadfilejob.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Quotient/user.h>
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/eventstats.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "roommanager.h"
|
||||
#include "windowcontroller.h"
|
||||
@@ -60,6 +60,7 @@ Controller::Controller(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
Connection::setUserType<NeoChatUser>();
|
||||
|
||||
setApplicationProxy();
|
||||
|
||||
@@ -124,21 +125,9 @@ Controller &Controller::instance()
|
||||
return _instance;
|
||||
}
|
||||
|
||||
void Controller::toggleWindow()
|
||||
void Controller::showWindow()
|
||||
{
|
||||
auto &instance = WindowController::instance();
|
||||
auto window = instance.window();
|
||||
if (window->isVisible()) {
|
||||
if (window->windowStates() & Qt::WindowMinimized) {
|
||||
window->showNormal();
|
||||
window->requestActivate();
|
||||
} else {
|
||||
window->close();
|
||||
}
|
||||
} else {
|
||||
instance.showAndRaiseWindow({});
|
||||
instance.window()->requestActivate();
|
||||
}
|
||||
WindowController::instance().showAndRaiseWindow(QString());
|
||||
}
|
||||
|
||||
void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
@@ -148,7 +137,7 @@ void Controller::logout(Connection *conn, bool serverSideLogout)
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsGroup("Accounts"_ls).remove(conn->userId());
|
||||
SettingsGroup("Accounts").remove(conn->userId());
|
||||
|
||||
QKeychain::DeletePasswordJob job(qAppName());
|
||||
job.setAutoDelete(true);
|
||||
@@ -212,7 +201,7 @@ void Controller::dropConnection(Connection *c)
|
||||
|
||||
void Controller::invokeLogin()
|
||||
{
|
||||
const auto accounts = SettingsGroup("Accounts"_ls).childGroups();
|
||||
const auto accounts = SettingsGroup("Accounts").childGroups();
|
||||
QString id = NeoChatConfig::self()->activeConnection();
|
||||
for (const auto &accountId : accounts) {
|
||||
AccountSettings account{accountId};
|
||||
@@ -226,7 +215,7 @@ void Controller::invokeLogin()
|
||||
AccountSettings account{accountId};
|
||||
QString accessToken;
|
||||
if (accessTokenLoadingJob->error() == QKeychain::Error::NoError) {
|
||||
accessToken = QString::fromLatin1(accessTokenLoadingJob->binaryData());
|
||||
accessToken = accessTokenLoadingJob->binaryData();
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
@@ -241,10 +230,10 @@ void Controller::invokeLogin()
|
||||
}
|
||||
});
|
||||
connect(connection, &Connection::loginError, this, [this, connection](const QString &error, const QString &) {
|
||||
if (error == "Unrecognised access token"_ls) {
|
||||
if (error == "Unrecognised access token") {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: Access Token invalid or revoked"));
|
||||
logout(connection, false);
|
||||
} else if (error == "Connection closed"_ls) {
|
||||
} else if (error == "Connection closed") {
|
||||
Q_EMIT errorOccured(i18n("Login Failed: %1", error));
|
||||
// Failed due to network connection issue. This might happen when the homeserver is
|
||||
// temporary down, or the user trying to re-launch NeoChat in a network that cannot
|
||||
@@ -342,7 +331,7 @@ bool Controller::supportSystemTray() const
|
||||
#ifdef Q_OS_ANDROID
|
||||
return false;
|
||||
#else
|
||||
auto de = QString::fromLatin1(qgetenv("XDG_CURRENT_DESKTOP"));
|
||||
QString de = getenv("XDG_CURRENT_DESKTOP");
|
||||
return de != QStringLiteral("GNOME") && de != QStringLiteral("Pantheon");
|
||||
#endif
|
||||
}
|
||||
@@ -354,18 +343,18 @@ void Controller::changePassword(Connection *connection, const QString ¤tPa
|
||||
if (job->error() == 103) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = currentPassword;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
authData["user"_ls] = connection->user()->id();
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, connection->user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
authData["session"] = replyData["session"];
|
||||
authData["password"] = currentPassword;
|
||||
authData["type"] = "m.login.password";
|
||||
authData["user"] = connection->user()->id();
|
||||
QJsonObject identifier = {{"type", "m.id.user"}, {"user", connection->user()->id()}};
|
||||
authData["identifier"] = identifier;
|
||||
NeochatChangePasswordJob *innerJob = connection->callApi<NeochatChangePasswordJob>(newPassword, false, authData);
|
||||
connect(innerJob, &BaseJob::success, this, [this]() {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Success);
|
||||
});
|
||||
connect(innerJob, &BaseJob::failure, this, [innerJob, this]() {
|
||||
if (innerJob->jsonData()["errcode"_ls] == "M_FORBIDDEN"_ls) {
|
||||
if (innerJob->jsonData()["errcode"] == "M_FORBIDDEN") {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Wrong);
|
||||
} else {
|
||||
Q_EMIT passwordStatus(PasswordStatus::Other);
|
||||
@@ -411,10 +400,10 @@ void Controller::setQuitOnLastWindowClosed()
|
||||
if (NeoChatConfig::self()->systemTray()) {
|
||||
m_trayIcon = new TrayIcon(this);
|
||||
m_trayIcon->show();
|
||||
connect(m_trayIcon, &TrayIcon::toggleWindow, this, &Controller::toggleWindow);
|
||||
connect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
} else {
|
||||
if (m_trayIcon) {
|
||||
disconnect(m_trayIcon, &TrayIcon::toggleWindow, this, &Controller::toggleWindow);
|
||||
disconnect(m_trayIcon, &TrayIcon::showWindow, this, &Controller::showWindow);
|
||||
delete m_trayIcon;
|
||||
m_trayIcon = nullptr;
|
||||
}
|
||||
@@ -460,7 +449,7 @@ void Controller::setActiveConnection(Connection *connection)
|
||||
Q_EMIT isOnlineChanged(true);
|
||||
});
|
||||
connect(connection, &Connection::requestFailed, this, [](BaseJob *job) {
|
||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
|
||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"].toString() == "M_TOO_LARGE"_ls) {
|
||||
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||
}
|
||||
});
|
||||
@@ -498,7 +487,7 @@ NeochatDeleteDeviceJob::NeochatDeleteDeviceJob(const QString &deviceId, const Om
|
||||
|
||||
void Controller::createRoom(const QString &name, const QString &topic)
|
||||
{
|
||||
auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, QString(), name, topic, QStringList());
|
||||
auto createRoomJob = m_connection->createRoom(Connection::PublishRoom, "", name, topic, QStringList());
|
||||
connect(createRoomJob, &CreateRoomJob::failure, this, [this, createRoomJob] {
|
||||
Q_EMIT errorOccured(i18n("Room creation failed: %1", createRoomJob->errorString()));
|
||||
});
|
||||
@@ -534,16 +523,16 @@ bool Controller::isOnline() const
|
||||
// TODO: Remove in favor of RoomManager::joinRoom
|
||||
void Controller::joinRoom(const QString &alias)
|
||||
{
|
||||
if (!alias.contains(":"_ls)) {
|
||||
if (!alias.contains(":")) {
|
||||
Q_EMIT errorOccured(i18n("The room id you are trying to join is not valid"));
|
||||
return;
|
||||
}
|
||||
|
||||
const auto knownServer = alias.mid(alias.indexOf(":"_ls) + 1);
|
||||
const auto knownServer = alias.mid(alias.indexOf(":") + 1);
|
||||
RoomManager::instance().joinRoom(m_connection, alias, QStringList{knownServer});
|
||||
}
|
||||
|
||||
void Controller::openOrCreateDirectChat(User *user)
|
||||
void Controller::openOrCreateDirectChat(NeoChatUser *user)
|
||||
{
|
||||
const auto existing = activeConnection()->directChats();
|
||||
|
||||
@@ -593,6 +582,11 @@ bool Controller::hasWindowSystem() const
|
||||
#endif
|
||||
}
|
||||
|
||||
bool Controller::encryptionSupported() const
|
||||
{
|
||||
return Quotient::encryptionSupported();
|
||||
}
|
||||
|
||||
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
|
||||
{
|
||||
// HACK: Workaround bug QTBUG 93281
|
||||
@@ -637,6 +631,12 @@ int Controller::activeConnectionIndex() const
|
||||
return result - m_accountRegistry.accounts().begin();
|
||||
}
|
||||
|
||||
int Controller::quotientMinorVersion() const
|
||||
{
|
||||
// TODO libQuotient 0.7: Replace with version function from libQuotient
|
||||
return 7;
|
||||
}
|
||||
|
||||
bool Controller::isFlatpak() const
|
||||
{
|
||||
#ifdef NEOCHAT_FLATPAK
|
||||
@@ -652,9 +652,9 @@ void Controller::setActiveAccountLabel(const QString &label)
|
||||
return;
|
||||
}
|
||||
QJsonObject json{
|
||||
{"account_label"_ls, label},
|
||||
{"account_label", label},
|
||||
};
|
||||
m_connection->setAccountData("org.kde.neochat.account_label"_ls, json);
|
||||
m_connection->setAccountData("org.kde.neochat.account_label", json);
|
||||
}
|
||||
|
||||
QString Controller::activeAccountLabel() const
|
||||
@@ -662,7 +662,7 @@ QString Controller::activeAccountLabel() const
|
||||
if (!m_connection) {
|
||||
return {};
|
||||
}
|
||||
return m_connection->accountDataJson("org.kde.neochat.account_label"_ls)["account_label"_ls].toString();
|
||||
return m_connection->accountDataJson("org.kde.neochat.account_label")["account_label"].toString();
|
||||
}
|
||||
|
||||
QVariantList Controller::getSupportedRoomVersions(Quotient::Connection *connection)
|
||||
@@ -672,9 +672,9 @@ QVariantList Controller::getSupportedRoomVersions(Quotient::Connection *connecti
|
||||
QVariantList supportedRoomVersions;
|
||||
for (const Quotient::Connection::SupportedRoomVersion &v : roomVersions) {
|
||||
QVariantMap roomVersionMap;
|
||||
roomVersionMap.insert("id"_ls, v.id);
|
||||
roomVersionMap.insert("status"_ls, v.status);
|
||||
roomVersionMap.insert("isStable"_ls, v.isStable());
|
||||
roomVersionMap.insert("id", v.id);
|
||||
roomVersionMap.insert("status", v.status);
|
||||
roomVersionMap.insert("isStable", v.isStable());
|
||||
supportedRoomVersions.append(roomVersionMap);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include <Quotient/settings.h>
|
||||
|
||||
class NeoChatRoom;
|
||||
class NeoChatUser;
|
||||
class TrayIcon;
|
||||
class QWindow;
|
||||
class QQuickTextDocument;
|
||||
@@ -22,7 +23,6 @@ namespace Quotient
|
||||
{
|
||||
class Connection;
|
||||
class Room;
|
||||
class User;
|
||||
}
|
||||
|
||||
namespace QKeychain
|
||||
@@ -87,6 +87,20 @@ class Controller : public QObject
|
||||
*/
|
||||
Q_PROPERTY(bool isOnline READ isOnline NOTIFY isOnlineChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the ecryption support has been enabled.
|
||||
*/
|
||||
Q_PROPERTY(bool encryptionSupported READ encryptionSupported CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The current minor version number of libQuotient being used.
|
||||
*
|
||||
* This is the only way to gate NeoChat features by libQuotient version in QML.
|
||||
*
|
||||
* @note No major version because libQuotient doesn't have any; All are 0.x.
|
||||
*/
|
||||
Q_PROPERTY(int quotientMinorVersion READ quotientMinorVersion CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief Whether NeoChat is running as a flatpak.
|
||||
*
|
||||
@@ -169,7 +183,7 @@ public:
|
||||
*
|
||||
* If a direct chat with the user doesn't exist one is created and then joined.
|
||||
*/
|
||||
Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user);
|
||||
Q_INVOKABLE void openOrCreateDirectChat(NeoChatUser *user);
|
||||
|
||||
[[nodiscard]] bool supportSystemTray() const;
|
||||
|
||||
@@ -180,6 +194,8 @@ public:
|
||||
|
||||
bool isOnline() const;
|
||||
|
||||
bool encryptionSupported() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the QNetworkProxy for the application.
|
||||
*
|
||||
@@ -187,6 +203,8 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE void setApplicationProxy();
|
||||
|
||||
int quotientMinorVersion() const;
|
||||
|
||||
bool isFlatpak() const;
|
||||
|
||||
/**
|
||||
@@ -234,7 +252,7 @@ private:
|
||||
|
||||
private Q_SLOTS:
|
||||
void invokeLogin();
|
||||
void toggleWindow();
|
||||
void showWindow();
|
||||
void setQuitOnLastWindowClosed();
|
||||
|
||||
Q_SIGNALS:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -10,29 +10,29 @@ ImagePackEventContent::ImagePackEventContent(const QJsonObject &json)
|
||||
{
|
||||
if (json.contains(QStringLiteral("pack"))) {
|
||||
pack = ImagePackEventContent::Pack{
|
||||
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["display_name"_ls]),
|
||||
fromJson<Omittable<QUrl>>(json["pack"_ls].toObject()["avatar_url"_ls]),
|
||||
fromJson<Omittable<QStringList>>(json["pack"_ls].toObject()["usage"_ls]),
|
||||
fromJson<Omittable<QString>>(json["pack"_ls].toObject()["attribution"_ls]),
|
||||
fromJson<Omittable<QString>>(json["pack"].toObject()["display_name"]),
|
||||
fromJson<Omittable<QUrl>>(json["pack"].toObject()["avatar_url"]),
|
||||
fromJson<Omittable<QStringList>>(json["pack"].toObject()["usage"]),
|
||||
fromJson<Omittable<QString>>(json["pack"].toObject()["attribution"]),
|
||||
};
|
||||
} else {
|
||||
pack = none;
|
||||
}
|
||||
|
||||
const auto &keys = json["images"_ls].toObject().keys();
|
||||
const auto &keys = json["images"].toObject().keys();
|
||||
for (const auto &k : keys) {
|
||||
Omittable<EventContent::ImageInfo> info;
|
||||
if (json["images"_ls][k].toObject().contains(QStringLiteral("info"))) {
|
||||
info = EventContent::ImageInfo(QUrl(json["images"_ls][k]["url"_ls].toString()), json["images"_ls][k]["info"_ls].toObject(), k);
|
||||
if (json["images"][k].toObject().contains(QStringLiteral("info"))) {
|
||||
info = EventContent::ImageInfo(QUrl(json["images"][k]["url"].toString()), json["images"][k]["info"].toObject(), k);
|
||||
} else {
|
||||
info = none;
|
||||
}
|
||||
images += ImagePackImage{
|
||||
k,
|
||||
fromJson<QUrl>(json["images"_ls][k]["url"_ls].toString()),
|
||||
fromJson<Omittable<QString>>(json["images"_ls][k]["body"_ls]),
|
||||
fromJson<QUrl>(json["images"][k]["url"].toString()),
|
||||
fromJson<Omittable<QString>>(json["images"][k]["body"]),
|
||||
info,
|
||||
fromJson<Omittable<QStringList>>(json["images"_ls][k]["usage"_ls]),
|
||||
fromJson<Omittable<QStringList>>(json["images"][k]["usage"]),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -42,20 +42,20 @@ void ImagePackEventContent::fillJson(QJsonObject *o) const
|
||||
if (pack) {
|
||||
QJsonObject packJson;
|
||||
if (pack->displayName) {
|
||||
packJson["display_name"_ls] = *pack->displayName;
|
||||
packJson["display_name"] = *pack->displayName;
|
||||
}
|
||||
if (pack->usage) {
|
||||
QJsonArray usageJson;
|
||||
for (const auto &usage : *pack->usage) {
|
||||
usageJson += usage;
|
||||
}
|
||||
packJson["usage"_ls] = usageJson;
|
||||
packJson["usage"] = usageJson;
|
||||
}
|
||||
if (pack->avatarUrl) {
|
||||
packJson["avatar_url"_ls] = pack->avatarUrl->toString();
|
||||
packJson["avatar_url"] = pack->avatarUrl->toString();
|
||||
}
|
||||
if (pack->attribution) {
|
||||
packJson["attribution"_ls] = *pack->attribution;
|
||||
packJson["attribution"] = *pack->attribution;
|
||||
}
|
||||
(*o)["pack"_ls] = packJson;
|
||||
}
|
||||
@@ -63,16 +63,16 @@ void ImagePackEventContent::fillJson(QJsonObject *o) const
|
||||
QJsonObject imagesJson;
|
||||
for (const auto &image : images) {
|
||||
QJsonObject imageJson;
|
||||
imageJson["url"_ls] = image.url.toString();
|
||||
imageJson["url"] = image.url.toString();
|
||||
if (image.body) {
|
||||
imageJson["body"_ls] = *image.body;
|
||||
imageJson["body"] = *image.body;
|
||||
}
|
||||
if (image.usage) {
|
||||
QJsonArray usageJson;
|
||||
for (const auto &usage : *image.usage) {
|
||||
usageJson += usage;
|
||||
}
|
||||
imageJson["usage"_ls] = usageJson;
|
||||
imageJson["usage"] = usageJson;
|
||||
}
|
||||
imagesJson[image.shortcode] = imageJson;
|
||||
}
|
||||
|
||||
@@ -12,12 +12,12 @@ PollStartEvent::PollStartEvent(const QJsonObject &obj)
|
||||
|
||||
int PollStartEvent::maxSelections() const
|
||||
{
|
||||
return contentJson()["org.matrix.msc3381.poll.start"_ls]["max_selections"_ls].toInt();
|
||||
return contentJson()["org.matrix.msc3381.poll.start"]["max_selections"].toInt();
|
||||
}
|
||||
|
||||
QString PollStartEvent::question() const
|
||||
{
|
||||
return contentJson()["org.matrix.msc3381.poll.start"_ls]["question"_ls]["body"_ls].toString();
|
||||
return contentJson()["org.matrix.msc3381.poll.start"]["question"]["body"].toString();
|
||||
}
|
||||
|
||||
PollResponseEvent::PollResponseEvent(const QJsonObject &obj)
|
||||
@@ -32,7 +32,7 @@ PollEndEvent::PollEndEvent(const QJsonObject &obj)
|
||||
|
||||
PollResponseEvent::PollResponseEvent(const QString &pollStartEventId, QStringList responses)
|
||||
: RoomEvent(basicJson(TypeId,
|
||||
{{"org.matrix.msc3381.poll.response"_ls, QJsonObject{{"answers"_ls, QJsonArray::fromStringList(responses)}}},
|
||||
{"m.relates_to"_ls, QJsonObject{{"rel_type"_ls, "m.reference"_ls}, {"event_id"_ls, pollStartEventId}}}}))
|
||||
{{"org.matrix.msc3381.poll.response", QJsonObject{{"answers", QJsonArray::fromStringList(responses)}}},
|
||||
{"m.relates_to", QJsonObject{{"rel_type", "m.reference"}, {"event_id", pollStartEventId}}}}))
|
||||
{
|
||||
}
|
||||
|
||||
@@ -62,14 +62,14 @@ void LinkPreviewer::loadUrlPreview()
|
||||
Q_EMIT loadedChanged();
|
||||
|
||||
auto conn = m_currentRoom->connection();
|
||||
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url);
|
||||
GetUrlPreviewJob *job = conn->callApi<GetUrlPreviewJob>(m_url.toString());
|
||||
|
||||
connect(job, &BaseJob::success, this, [this, job, conn]() {
|
||||
const auto json = job->jsonData();
|
||||
m_title = json["og:title"_ls].toString().trimmed();
|
||||
m_description = json["og:description"_ls].toString().trimmed().replace("\n"_ls, " "_ls);
|
||||
m_title = json["og:title"].toString().trimmed();
|
||||
m_description = json["og:description"].toString().trimmed().replace("\n", " ");
|
||||
|
||||
auto imageUrl = QUrl(json["og:image"_ls].toString());
|
||||
auto imageUrl = QUrl(json["og:image"].toString());
|
||||
if (imageUrl.isValid() && imageUrl.scheme() == QStringLiteral("mxc")) {
|
||||
m_imageSource = conn->makeMediaUrl(imageUrl);
|
||||
} else {
|
||||
|
||||
@@ -87,32 +87,32 @@ public:
|
||||
QMutexLocker locker(&mutex);
|
||||
QByteArray buf;
|
||||
QTextStream str(&buf);
|
||||
str << QDateTime::currentDateTime().toString(Qt::ISODate) << QStringLiteral(" [");
|
||||
str << QDateTime::currentDateTime().toString(Qt::ISODate) << " [";
|
||||
switch (type) {
|
||||
case QtDebugMsg:
|
||||
str << QStringLiteral("DEBUG");
|
||||
str << "DEBUG";
|
||||
break;
|
||||
case QtInfoMsg:
|
||||
str << QStringLiteral("INFO ");
|
||||
str << "INFO ";
|
||||
break;
|
||||
case QtWarningMsg:
|
||||
str << QStringLiteral("WARN ");
|
||||
str << "WARN ";
|
||||
break;
|
||||
case QtFatalMsg:
|
||||
str << QStringLiteral("FATAL");
|
||||
str << "FATAL";
|
||||
break;
|
||||
case QtCriticalMsg:
|
||||
str << QStringLiteral("CRITICAL");
|
||||
str << "CRITICAL";
|
||||
break;
|
||||
}
|
||||
str << QStringLiteral("] ") << context.category << QStringLiteral(": ");
|
||||
str << "] " << context.category << ": ";
|
||||
if (context.file && *context.file && context.line) {
|
||||
str << context.file << QStringLiteral(":") << context.line << QStringLiteral(": ");
|
||||
str << context.file << ":" << context.line << ": ";
|
||||
}
|
||||
if (context.function && *context.function) {
|
||||
str << context.function << QStringLiteral(": ");
|
||||
str << context.function << ": ";
|
||||
}
|
||||
str << message << QStringLiteral("\n");
|
||||
str << message << "\n";
|
||||
str.flush();
|
||||
file.write(buf.constData(), buf.size());
|
||||
file.flush();
|
||||
@@ -129,18 +129,18 @@ public:
|
||||
if (file.isOpen()) {
|
||||
file.close();
|
||||
}
|
||||
const auto &filePath = QStringLiteral("%1%2%3").arg(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation), QDir::separator(), appName);
|
||||
auto filePath = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator() + appName;
|
||||
|
||||
QDir dir(QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + QDir::separator());
|
||||
auto entryList = dir.entryList({appName + QStringLiteral(".*")});
|
||||
std::sort(entryList.begin(), entryList.end(), [](const auto &left, const auto &right) {
|
||||
auto leftIndex = left.split(QStringLiteral(".")).last().toInt();
|
||||
auto rightIndex = right.split(QStringLiteral(".")).last().toInt();
|
||||
auto leftIndex = left.split(".").last().toInt();
|
||||
auto rightIndex = right.split(".").last().toInt();
|
||||
return leftIndex > rightIndex;
|
||||
});
|
||||
for (const auto &entry : entryList) {
|
||||
bool ok = false;
|
||||
const auto index = entry.split(QStringLiteral(".")).last().toInt(&ok);
|
||||
const auto index = entry.split(".").last().toInt(&ok);
|
||||
if (!ok) {
|
||||
continue;
|
||||
}
|
||||
@@ -151,8 +151,8 @@ public:
|
||||
file.remove();
|
||||
continue;
|
||||
}
|
||||
const auto &newName = QStringLiteral("%1.%2").arg(filePath, QString::number(index + 1));
|
||||
const auto success = file.copy(newName);
|
||||
const QString newName = filePath + QStringLiteral(".%1").arg(index + 1);
|
||||
const bool success = file.copy(newName);
|
||||
if (success) {
|
||||
file.remove();
|
||||
} else {
|
||||
@@ -216,7 +216,7 @@ void initLogging()
|
||||
oldCategoryFilter = QLoggingCategory::installFilter(filter);
|
||||
oldHandler = qInstallMessageHandler(messageHandler);
|
||||
sInstance->setOrigHandler(oldHandler);
|
||||
sInstance->setName(QStringLiteral("neochat.log"));
|
||||
sInstance->setName("neochat.log");
|
||||
}
|
||||
|
||||
#include "logger.moc"
|
||||
|
||||
@@ -33,12 +33,12 @@ void Login::init()
|
||||
|
||||
connect(this, &Login::matrixIdChanged, this, [this]() {
|
||||
setHomeserverReachable(false);
|
||||
QRegularExpression validator(QStringLiteral("^\\@?[a-zA-Z0-9\\._=\\-/]+\\:[a-zA-Z0-9\\-]+(\\.[a-zA-Z0-9\\-]+)*(\\:[0-9]+)?$"));
|
||||
QRegularExpression validator("^\\@?[a-zA-Z0-9\\._=\\-/]+\\:[a-zA-Z0-9\\-]+(\\.[a-zA-Z0-9\\-]+)*(\\:[0-9]+)?$");
|
||||
if (!validator.match(m_matrixId).hasMatch()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_matrixId == QLatin1Char('@')) {
|
||||
if (m_matrixId == "@") {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -120,8 +120,8 @@ QString Login::matrixId() const
|
||||
void Login::setMatrixId(const QString &matrixId)
|
||||
{
|
||||
m_matrixId = matrixId;
|
||||
if (!m_matrixId.startsWith(QLatin1Char('@'))) {
|
||||
m_matrixId.prepend(QLatin1Char('@'));
|
||||
if (!m_matrixId.startsWith('@')) {
|
||||
m_matrixId.prepend('@');
|
||||
}
|
||||
Q_EMIT matrixIdChanged();
|
||||
}
|
||||
@@ -155,7 +155,7 @@ void Login::login()
|
||||
|
||||
// Some servers do not have a .well_known file. So we login via the username part from the mxid,
|
||||
// rather than with the full mxid, as that would lead to an invalid user.
|
||||
auto username = m_matrixId.mid(1, m_matrixId.indexOf(QLatin1Char(':')) - 1);
|
||||
auto username = m_matrixId.mid(1, m_matrixId.indexOf(":") - 1);
|
||||
m_connection->loginWithPassword(username, m_password, m_deviceName, QString());
|
||||
}
|
||||
|
||||
|
||||
57
src/main.cpp
57
src/main.cpp
@@ -3,9 +3,7 @@
|
||||
|
||||
#include <QCommandLineParser>
|
||||
#include <QIcon>
|
||||
#include <QNetworkDiskCache>
|
||||
#include <QNetworkProxyFactory>
|
||||
#include <QObject>
|
||||
#include <QQmlApplicationEngine>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlNetworkAccessManagerFactory>
|
||||
@@ -30,11 +28,10 @@
|
||||
|
||||
#include "neochat-version.h"
|
||||
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/keyverificationsession.h>
|
||||
#include <Quotient/accountregistry.h>
|
||||
#include <Quotient/networkaccessmanager.h>
|
||||
#include <Quotient/room.h>
|
||||
#include <Quotient/user.h>
|
||||
#include <Quotient/util.h>
|
||||
|
||||
#include "actionshandler.h"
|
||||
@@ -78,6 +75,7 @@
|
||||
#include "models/webshortcutmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "neochatuser.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "pollhandler.h"
|
||||
#include "roommanager.h"
|
||||
@@ -90,6 +88,7 @@
|
||||
#endif
|
||||
#include "models/completionmodel.h"
|
||||
#include "models/statemodel.h"
|
||||
#include "neochatuser.h"
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
#include "runner.h"
|
||||
@@ -106,17 +105,7 @@ class NetworkAccessManagerFactory : public QQmlNetworkAccessManagerFactory
|
||||
{
|
||||
QNetworkAccessManager *create(QObject *) override
|
||||
{
|
||||
auto nam = NetworkAccessManager::instance();
|
||||
nam->setRedirectPolicy(QNetworkRequest::NoLessSafeRedirectPolicy);
|
||||
|
||||
nam->enableStrictTransportSecurityStore(true, QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/hsts/"));
|
||||
nam->setStrictTransportSecurityEnabled(true);
|
||||
|
||||
auto namDiskCache = new QNetworkDiskCache(nam);
|
||||
namDiskCache->setCacheDirectory(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + QLatin1String("/nam/"));
|
||||
nam->setCache(namDiskCache);
|
||||
|
||||
return nam;
|
||||
return NetworkAccessManager::instance();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -143,7 +132,7 @@ int main(int argc, char *argv[])
|
||||
QGuiApplication app(argc, argv);
|
||||
QQuickStyle::setStyle(QStringLiteral("org.kde.breeze"));
|
||||
#else
|
||||
QIcon::setFallbackThemeName("breeze"_ls);
|
||||
QIcon::setFallbackThemeName("breeze");
|
||||
QApplication app(argc, argv);
|
||||
// Default to org.kde.desktop style unless the user forces another style
|
||||
if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
|
||||
@@ -165,7 +154,7 @@ int main(int argc, char *argv[])
|
||||
#endif
|
||||
KLocalizedString::setApplicationDomain("neochat");
|
||||
|
||||
QGuiApplication::setOrganizationName("KDE"_ls);
|
||||
QGuiApplication::setOrganizationName("KDE");
|
||||
|
||||
KAboutData about(QStringLiteral("neochat"),
|
||||
i18n("NeoChat"),
|
||||
@@ -196,14 +185,15 @@ int main(int argc, char *argv[])
|
||||
initLogging();
|
||||
|
||||
#if Quotient_VERSION_MINOR == 8
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
Connection::setEncryptionDefault(true);
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef NEOCHAT_FLATPAK
|
||||
// Copy over the included FontConfig configuration to the
|
||||
// app's config dir:
|
||||
QFile::copy(QStringLiteral("/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf"),
|
||||
QStringLiteral("/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"));
|
||||
QFile::copy("/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf", "/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf");
|
||||
#endif
|
||||
|
||||
Clipboard clipboard;
|
||||
@@ -265,13 +255,13 @@ int main(int argc, char *argv[])
|
||||
qmlRegisterType<AccountEmoticonModel>("org.kde.neochat", 1, 0, "AccountEmoticonModel");
|
||||
qmlRegisterType<EmoticonFilterModel>("org.kde.neochat", 1, 0, "EmoticonFilterModel");
|
||||
qmlRegisterType<DelegateSizeHelper>("org.kde.neochat", 1, 0, "DelegateSizeHelper");
|
||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM"_ls);
|
||||
qmlRegisterUncreatableType<PushNotificationKind>("org.kde.neochat", 1, 0, "PushNotificationKind", "ENUM"_ls);
|
||||
qmlRegisterUncreatableType<PushNotificationSection>("org.kde.neochat", 1, 0, "PushNotificationSection", "ENUM"_ls);
|
||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM"_ls);
|
||||
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM"_ls);
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM"_ls);
|
||||
qmlRegisterUncreatableType<User>("org.kde.neochat", 1, 0, "User", {});
|
||||
qmlRegisterUncreatableType<RoomMessageEvent>("org.kde.neochat", 1, 0, "RoomMessageEvent", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationKind>("org.kde.neochat", 1, 0, "PushNotificationKind", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationSection>("org.kde.neochat", 1, 0, "PushNotificationSection", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationState>("org.kde.neochat", 1, 0, "PushNotificationState", "ENUM");
|
||||
qmlRegisterUncreatableType<PushNotificationAction>("org.kde.neochat", 1, 0, "PushNotificationAction", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatRoomType>("org.kde.neochat", 1, 0, "NeoChatRoomType", "ENUM");
|
||||
qmlRegisterUncreatableType<NeoChatUser>("org.kde.neochat", 1, 0, "NeoChatUser", {});
|
||||
qmlRegisterUncreatableType<NeoChatRoom>("org.kde.neochat", 1, 0, "NeoChatRoom", {});
|
||||
|
||||
qRegisterMetaType<User *>("User*");
|
||||
@@ -281,16 +271,18 @@ int main(int argc, char *argv[])
|
||||
qRegisterMetaType<Connection *>("Connection*");
|
||||
qRegisterMetaType<MessageEventType>("MessageEventType");
|
||||
qRegisterMetaType<NeoChatRoom *>("NeoChatRoom*");
|
||||
qRegisterMetaType<User *>("User*");
|
||||
qRegisterMetaType<NeoChatUser *>("NeoChatUser*");
|
||||
qRegisterMetaType<GetRoomEventsJob *>("GetRoomEventsJob*");
|
||||
qRegisterMetaType<QMimeType>("QMimeType");
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
qRegisterMetaType<KeyVerificationSession *>("KeyVerificationSession*");
|
||||
qmlRegisterUncreatableType<KeyVerificationSession>("org.kde.neochat", 1, 0, "KeyVerificationSession", {});
|
||||
qRegisterMetaType<QVector<EmojiEntry>>("QVector<EmojiEntry>");
|
||||
#endif
|
||||
qmlRegisterSingletonType("org.kde.neochat", 1, 0, "About", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
|
||||
return engine->toScriptValue(KAboutData::applicationData());
|
||||
});
|
||||
qmlRegisterSingletonType(QUrl("qrc:/OsmLocationPlugin.qml"_ls), "org.kde.neochat", 1, 0, "OsmLocationPlugin");
|
||||
qmlRegisterSingletonType(QUrl("qrc:/OsmLocationPlugin.qml"), "org.kde.neochat", 1, 0, "OsmLocationPlugin");
|
||||
qmlRegisterSingletonType("org.kde.neochat", 1, 0, "LocationHelper", [](QQmlEngine *engine, QJSEngine *) -> QJSValue {
|
||||
return engine->toScriptValue(LocationHelper());
|
||||
});
|
||||
@@ -333,18 +325,11 @@ int main(int argc, char *argv[])
|
||||
QCommandLineParser parser;
|
||||
parser.setApplicationDescription(i18n("Client for the matrix communication protocol"));
|
||||
parser.addPositionalArgument(QStringLiteral("urls"), i18n("Supports matrix: url scheme"));
|
||||
parser.addOption(QCommandLineOption("ignore-ssl-errors"_ls, i18n("Ignore all SSL Errors, e.g., unsigned certificates.")));
|
||||
|
||||
about.setupCommandLine(&parser);
|
||||
parser.process(app);
|
||||
about.processCommandLine(&parser);
|
||||
|
||||
if (parser.isSet("ignore-ssl-errors"_ls)) {
|
||||
QObject::connect(NetworkAccessManager::instance(), &QNetworkAccessManager::sslErrors, NetworkAccessManager::instance(), [](QNetworkReply *reply) {
|
||||
reply->ignoreSslErrors();
|
||||
});
|
||||
}
|
||||
|
||||
engine.addImageProvider(QLatin1String("mxc"), new MatrixImageProvider);
|
||||
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
|
||||
|
||||
@@ -359,7 +344,7 @@ int main(int argc, char *argv[])
|
||||
|
||||
#ifdef HAVE_RUNNER
|
||||
Runner runner;
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, &runner, QDBusConnection::ExportScriptableContents);
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner", &runner, QDBusConnection::ExportScriptableContents);
|
||||
#endif
|
||||
|
||||
QWindow *window = windowFromEngine(&engine);
|
||||
|
||||
@@ -25,13 +25,13 @@ ThumbnailResponse::ThumbnailResponse(QString id, QSize size)
|
||||
mediaId,
|
||||
QString::number(requestedSize.width()),
|
||||
QString::number(requestedSize.height())))
|
||||
, errorStr("Image request hasn't started"_ls)
|
||||
, errorStr("Image request hasn't started")
|
||||
{
|
||||
if (requestedSize.isEmpty()) {
|
||||
requestedSize.setHeight(100);
|
||||
requestedSize.setWidth(100);
|
||||
}
|
||||
if (mediaId.count(QLatin1Char('/')) != 1) {
|
||||
if (mediaId.count('/') != 1) {
|
||||
if (mediaId.startsWith(QLatin1Char('/'))) {
|
||||
mediaId = mediaId.mid(1);
|
||||
} else {
|
||||
|
||||
@@ -138,7 +138,7 @@ QCoro::Task<void> AccountEmoticonModel::doSetEmoticonImage(int index, QUrl sourc
|
||||
if (job->error() != BaseJob::NoError) {
|
||||
co_return;
|
||||
}
|
||||
m_images->images[index].url = job->contentUri();
|
||||
m_images->images[index].url = job->contentUri().toString();
|
||||
m_images->images[index].info = none;
|
||||
QJsonObject data;
|
||||
m_images->fillJson(&data);
|
||||
|
||||
@@ -14,10 +14,9 @@
|
||||
using Action = ActionsModel::Action;
|
||||
using namespace Quotient;
|
||||
|
||||
QStringList rainbowColors{"#ff2b00"_ls, "#ff5500"_ls, "#ff8000"_ls, "#ffaa00"_ls, "#ffd500"_ls, "#ffff00"_ls, "#d4ff00"_ls, "#aaff00"_ls, "#80ff00"_ls,
|
||||
"#55ff00"_ls, "#2bff00"_ls, "#00ff00"_ls, "#00ff2b"_ls, "#00ff55"_ls, "#00ff80"_ls, "#00ffaa"_ls, "#00ffd5"_ls, "#00ffff"_ls,
|
||||
"#00d4ff"_ls, "#00aaff"_ls, "#007fff"_ls, "#0055ff"_ls, "#002bff"_ls, "#0000ff"_ls, "#2a00ff"_ls, "#5500ff"_ls, "#7f00ff"_ls,
|
||||
"#aa00ff"_ls, "#d400ff"_ls, "#ff00ff"_ls, "#ff00d4"_ls, "#ff00aa"_ls, "#ff0080"_ls, "#ff0055"_ls, "#ff002b"_ls, "#ff0000"_ls};
|
||||
QStringList rainbowColors{"#ff2b00", "#ff5500", "#ff8000", "#ffaa00", "#ffd500", "#ffff00", "#d4ff00", "#aaff00", "#80ff00", "#55ff00", "#2bff00", "#00ff00",
|
||||
"#00ff2b", "#00ff55", "#00ff80", "#00ffaa", "#00ffd5", "#00ffff", "#00d4ff", "#00aaff", "#007fff", "#0055ff", "#002bff", "#0000ff",
|
||||
"#2a00ff", "#5500ff", "#7f00ff", "#aa00ff", "#d400ff", "#ff00ff", "#ff00d4", "#ff00aa", "#ff0080", "#ff0055", "#ff002b", "#ff0000"};
|
||||
|
||||
auto leaveRoomLambda = [](const QString &text, NeoChatRoom *room) {
|
||||
if (text.isEmpty()) {
|
||||
@@ -261,7 +260,7 @@ QVector<ActionsModel::Action> actions{
|
||||
}
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("Knocking room <roomname>.", "Knocking room %1.", text));
|
||||
auto connection = Controller::instance().activeConnection();
|
||||
const auto knownServer = roomName.mid(roomName.indexOf(":"_ls) + 1);
|
||||
const auto knownServer = roomName.mid(roomName.indexOf(":") + 1);
|
||||
if (parts.length() >= 2) {
|
||||
RoomManager::instance().knockRoom(connection, roomName, parts[1], QStringList{knownServer});
|
||||
} else {
|
||||
@@ -451,7 +450,7 @@ QVector<ActionsModel::Action> actions{
|
||||
i18nc("You are not allowed to ban <username> from this room.", "You are not allowed to ban %1 from this room.", parts[0]));
|
||||
return QString();
|
||||
}
|
||||
room->ban(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
|
||||
room->ban(parts[0], parts.size() > 1 ? parts.mid(1).join(" ") : QString());
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was banned from this room.", "%1 was banned from this room.", parts[0]));
|
||||
return QString();
|
||||
},
|
||||
@@ -528,7 +527,7 @@ QVector<ActionsModel::Action> actions{
|
||||
i18nc("You are not allowed to kick <username> from this room", "You are not allowed to kick %1 from this room.", parts[0]));
|
||||
return QString();
|
||||
}
|
||||
room->kickMember(parts[0], parts.size() > 1 ? parts.mid(1).join(QLatin1Char(' ')) : QString());
|
||||
room->kickMember(parts[0], parts.size() > 1 ? parts.mid(1).join(" ") : QString());
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> was kicked from this room.", "%1 was kicked from this room.", parts[0]));
|
||||
return QString();
|
||||
},
|
||||
|
||||
@@ -75,13 +75,13 @@ QString CollapseStateProxyModel::aggregateEventToString(int sourceRow) const
|
||||
}
|
||||
}
|
||||
chunks.removeDuplicates();
|
||||
QString text = QStringLiteral("<style>a {text-decoration: none;}</style>"); // There can be links in the event text so make sure all are styled.
|
||||
QString text = "<style>a {text-decoration: none;}</style>"; // There can be links in the event text so make sure all are styled.
|
||||
// The author text is either "n users" if > 1 user or the matrix.to link to a single user.
|
||||
QString userText = uniqueAuthors.length() > 1 ? i18ncp("n users", " %1 user ", " %1 users ", uniqueAuthors.length())
|
||||
: QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a> ")
|
||||
.arg(uniqueAuthors[0].toMap()[QStringLiteral("id")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("color")].toString(),
|
||||
uniqueAuthors[0].toMap()[QStringLiteral("displayName")].toString().toHtmlEscaped());
|
||||
.arg(uniqueAuthors[0].toMap()["id"].toString(),
|
||||
uniqueAuthors[0].toMap()["color"].toString(),
|
||||
uniqueAuthors[0].toMap()["displayName"].toString().toHtmlEscaped());
|
||||
text += userText;
|
||||
text += chunks.takeFirst();
|
||||
|
||||
@@ -104,9 +104,9 @@ QVariantList CollapseStateProxyModel::stateEventsList(int sourceRow) const
|
||||
QVariantList stateEvents;
|
||||
for (int i = sourceRow; i >= 0; i--) {
|
||||
auto nextState = QVariantMap{
|
||||
{QStringLiteral("author"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||
{QStringLiteral("authorDisplayName"), sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||
{QStringLiteral("text"), sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
{"author", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorRole)},
|
||||
{"authorDisplayName", sourceModel()->data(sourceModel()->index(i, 0), MessageEventModel::AuthorDisplayNameRole).toString()},
|
||||
{"text", sourceModel()->data(sourceModel()->index(i, 0), Qt::DisplayRole).toString()},
|
||||
};
|
||||
stateEvents.append(nextState);
|
||||
if (i > 0
|
||||
|
||||
@@ -66,8 +66,8 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
if (m_autoCompletionType == Command) {
|
||||
if (role == DisplayNameRole) {
|
||||
return QStringLiteral("%1 %2").arg(m_filterModel->data(filterIndex, ActionsModel::Prefix).toString(),
|
||||
m_filterModel->data(filterIndex, ActionsModel::Parameters).toString());
|
||||
return m_filterModel->data(filterIndex, ActionsModel::Prefix).toString() + QStringLiteral(" ")
|
||||
+ m_filterModel->data(filterIndex, ActionsModel::Parameters).toString();
|
||||
}
|
||||
if (role == SubtitleRole) {
|
||||
return m_filterModel->data(filterIndex, ActionsModel::Description);
|
||||
@@ -87,8 +87,7 @@ QVariant CompletionModel::data(const QModelIndex &index, int role) const
|
||||
return m_filterModel->data(filterIndex, RoomListModel::CanonicalAliasRole);
|
||||
}
|
||||
if (role == IconNameRole) {
|
||||
return QStringLiteral("mxc://%1")
|
||||
.arg(m_roomListModel->connection()->makeMediaUrl(m_filterModel->data(filterIndex, RoomListModel::AvatarRole).toUrl()).toString());
|
||||
return m_filterModel->data(filterIndex, RoomListModel::AvatarRole);
|
||||
}
|
||||
}
|
||||
if (m_autoCompletionType == Emoji) {
|
||||
|
||||
@@ -40,12 +40,12 @@ class CompletionModel : public QAbstractListModel
|
||||
*
|
||||
* @sa AutoCompletionType
|
||||
*/
|
||||
Q_PROPERTY(AutoCompletionType autoCompletionType READ autoCompletionType NOTIFY autoCompletionTypeChanged)
|
||||
Q_PROPERTY(AutoCompletionType autoCompletionType READ autoCompletionType NOTIFY autoCompletionTypeChanged);
|
||||
|
||||
/**
|
||||
* @brief The RoomListModel to be used for room completions.
|
||||
*/
|
||||
Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged)
|
||||
Q_PROPERTY(RoomListModel *roomListModel READ roomListModel WRITE setRoomListModel NOTIFY roomListModelChanged);
|
||||
|
||||
public:
|
||||
/**
|
||||
|
||||
@@ -11,11 +11,6 @@ bool CompletionProxyModel::filterAcceptsRow(int sourceRow, const QModelIndex &so
|
||||
if (m_filterText.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceModel()->data(sourceModel()->index(sourceRow, 0), filterRole()).toString().isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (sourceModel()->data(sourceModel()->index(sourceRow, 0), filterRole()).toString().startsWith(m_filterText, Qt::CaseInsensitive)
|
||||
&& !m_fullText.startsWith(sourceModel()->data(sourceModel()->index(sourceRow, 0), filterRole()).toString()))
|
||||
|| (m_secondaryFilterRole != -1
|
||||
|
||||
@@ -21,14 +21,14 @@ void CustomEmojiModel::fetchEmojis()
|
||||
return;
|
||||
}
|
||||
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes");
|
||||
if (data == nullptr) {
|
||||
return;
|
||||
}
|
||||
QJsonObject emojis = data->contentJson()["images"_ls].toObject();
|
||||
QJsonObject emojis = data->contentJson()["images"].toObject();
|
||||
|
||||
// TODO: Remove with stable migration
|
||||
const auto legacyEmojis = data->contentJson()["emoticons"_ls].toObject();
|
||||
const auto legacyEmojis = data->contentJson()["emoticons"].toObject();
|
||||
for (const auto &emoji : legacyEmojis.keys()) {
|
||||
if (!emojis.contains(emoji)) {
|
||||
emojis[emoji] = legacyEmojis[emoji];
|
||||
@@ -41,9 +41,9 @@ void CustomEmojiModel::fetchEmojis()
|
||||
for (const auto &emoji : emojis.keys()) {
|
||||
const auto &data = emojis[emoji];
|
||||
|
||||
const auto e = emoji.startsWith(":"_ls) ? emoji : (QStringLiteral(":") + emoji + QStringLiteral(":"));
|
||||
const auto e = emoji.startsWith(":") ? emoji : (QStringLiteral(":") + emoji + QStringLiteral(":"));
|
||||
|
||||
m_emojis << CustomEmoji{e, data.toObject()["url"_ls].toString(), QRegularExpression(e)};
|
||||
m_emojis << CustomEmoji{e, data.toObject()["url"].toString(), QRegularExpression(e)};
|
||||
}
|
||||
|
||||
endResetModel();
|
||||
@@ -56,19 +56,19 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
auto job = Controller::instance().activeConnection()->uploadFile(location.toLocalFile());
|
||||
|
||||
connect(job, &BaseJob::success, this, [name, location, job] {
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes");
|
||||
auto json = data != nullptr ? data->contentJson() : QJsonObject();
|
||||
auto emojiData = json["images"_ls].toObject();
|
||||
auto emojiData = json["images"].toObject();
|
||||
|
||||
QString url;
|
||||
url = job->contentUri().toString();
|
||||
|
||||
QImage image(location.toLocalFile());
|
||||
QJsonObject imageInfo;
|
||||
imageInfo["w"_ls] = image.width();
|
||||
imageInfo["h"_ls] = image.height();
|
||||
imageInfo["mimetype"_ls] = QMimeDatabase().mimeTypeForFile(location.toLocalFile()).name();
|
||||
imageInfo["size"_ls] = image.sizeInBytes();
|
||||
imageInfo["w"] = image.width();
|
||||
imageInfo["h"] = image.height();
|
||||
imageInfo["mimetype"] = QMimeDatabase().mimeTypeForFile(location.toLocalFile()).name();
|
||||
imageInfo["size"] = image.sizeInBytes();
|
||||
|
||||
emojiData[QStringLiteral("%1").arg(name)] = QJsonObject({
|
||||
{QStringLiteral("url"), url},
|
||||
@@ -77,8 +77,8 @@ void CustomEmojiModel::addEmoji(const QString &name, const QUrl &location)
|
||||
{"usage"_ls, "emoticon"_ls},
|
||||
});
|
||||
|
||||
json["images"_ls] = emojiData;
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
json["images"] = emojiData;
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes", json);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -86,30 +86,30 @@ void CustomEmojiModel::removeEmoji(const QString &name)
|
||||
{
|
||||
using namespace Quotient;
|
||||
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes"_ls);
|
||||
const auto &data = Controller::instance().activeConnection()->accountData("im.ponies.user_emotes");
|
||||
Q_ASSERT(data);
|
||||
auto json = data->contentJson();
|
||||
const QString _name = name.mid(1).chopped(1);
|
||||
auto emojiData = json["images"_ls].toObject();
|
||||
auto emojiData = json["images"].toObject();
|
||||
|
||||
if (emojiData.contains(name)) {
|
||||
emojiData.remove(name);
|
||||
json["images"_ls] = emojiData;
|
||||
json["images"] = emojiData;
|
||||
}
|
||||
if (emojiData.contains(_name)) {
|
||||
emojiData.remove(_name);
|
||||
json["images"_ls] = emojiData;
|
||||
json["images"] = emojiData;
|
||||
}
|
||||
emojiData = json["emoticons"_ls].toObject();
|
||||
emojiData = json["emoticons"].toObject();
|
||||
if (emojiData.contains(name)) {
|
||||
emojiData.remove(name);
|
||||
json["emoticons"_ls] = emojiData;
|
||||
json["emoticons"] = emojiData;
|
||||
}
|
||||
if (emojiData.contains(_name)) {
|
||||
emojiData.remove(_name);
|
||||
json["emoticons"_ls] = emojiData;
|
||||
json["emoticons"] = emojiData;
|
||||
}
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes"_ls, json);
|
||||
Controller::instance().activeConnection()->setAccountData("im.ponies.user_emotes", json);
|
||||
}
|
||||
|
||||
CustomEmojiModel::CustomEmojiModel(QObject *parent)
|
||||
@@ -174,7 +174,7 @@ QHash<int, QByteArray> CustomEmojiModel::roleNames() const
|
||||
|
||||
QString CustomEmojiModel::preprocessText(const QString &text)
|
||||
{
|
||||
auto parts = text.split("```"_ls);
|
||||
auto parts = text.split("```");
|
||||
bool skip = true;
|
||||
for (auto &part : parts) {
|
||||
skip = !skip;
|
||||
@@ -187,7 +187,7 @@ QString CustomEmojiModel::preprocessText(const QString &text)
|
||||
QStringLiteral(R"(<img data-mx-emoticon="" src="%1" alt="%2" title="%2" height="32" vertical-align="middle" />)").arg(emoji.url, emoji.name));
|
||||
}
|
||||
}
|
||||
return parts.join("```"_ls);
|
||||
return parts.join("```");
|
||||
}
|
||||
|
||||
QVariantList CustomEmojiModel::filterModel(const QString &filter)
|
||||
|
||||
@@ -65,6 +65,7 @@ QVariant DevicesModel::data(const QModelIndex &index, int role) const
|
||||
if (device.deviceId == m_connection->deviceId()) {
|
||||
return This;
|
||||
}
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
if (!m_connection->isKnownE2eeCapableDevice(m_connection->userId(), device.deviceId)) {
|
||||
return Unencrypted;
|
||||
}
|
||||
@@ -73,6 +74,9 @@ QVariant DevicesModel::data(const QModelIndex &index, int role) const
|
||||
} else {
|
||||
return Unverified;
|
||||
}
|
||||
#else
|
||||
return Unverified;
|
||||
#endif
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -113,11 +117,11 @@ void DevicesModel::logout(const QString &deviceId, const QString &password)
|
||||
if (job->error() != BaseJob::Success) {
|
||||
QJsonObject replyData = job->jsonData();
|
||||
QJsonObject authData;
|
||||
authData["session"_ls] = replyData["session"_ls];
|
||||
authData["password"_ls] = password;
|
||||
authData["type"_ls] = "m.login.password"_ls;
|
||||
QJsonObject identifier = {{"type"_ls, "m.id.user"_ls}, {"user"_ls, Controller::instance().activeConnection()->user()->id()}};
|
||||
authData["identifier"_ls] = identifier;
|
||||
authData["session"] = replyData["session"];
|
||||
authData["password"] = password;
|
||||
authData["type"] = "m.login.password";
|
||||
QJsonObject identifier = {{"type", "m.id.user"}, {"user", Controller::instance().activeConnection()->user()->id()}};
|
||||
authData["identifier"] = identifier;
|
||||
auto *innerJob = Controller::instance().activeConnection()->callApi<NeochatDeleteDeviceJob>(m_devices[index].deviceId, authData);
|
||||
connect(innerJob, &BaseJob::success, this, onSuccess);
|
||||
} else {
|
||||
@@ -157,6 +161,7 @@ void DevicesModel::setConnection(Connection *connection)
|
||||
Q_EMIT connectionChanged();
|
||||
fetchDevices();
|
||||
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
connect(m_connection, &Connection::sessionVerified, this, [this](const QString &userId, const QString &deviceId) {
|
||||
Q_UNUSED(deviceId);
|
||||
if (userId == Controller::instance().activeConnection()->userId()) {
|
||||
@@ -166,6 +171,7 @@ void DevicesModel::setConnection(Connection *connection)
|
||||
connect(m_connection, &Connection::finishedQueryingKeys, this, [this]() {
|
||||
fetchDevices();
|
||||
});
|
||||
#endif
|
||||
}
|
||||
|
||||
#include "moc_devicesmodel.cpp"
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
class DevicesProxyModel : public QSortFilterProxyModel
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int type READ type WRITE setType NOTIFY typeChanged)
|
||||
Q_PROPERTY(int type READ type WRITE setType NOTIFY typeChanged);
|
||||
|
||||
public:
|
||||
DevicesProxyModel(QObject *parent = nullptr);
|
||||
|
||||
@@ -63,7 +63,7 @@ QHash<int, QByteArray> EmojiModel::roleNames() const
|
||||
|
||||
QVariantList EmojiModel::history() const
|
||||
{
|
||||
return m_settings.value(QStringLiteral("Editor/emojis"), QVariantList()).toList();
|
||||
return m_settings.value("Editor/emojis", QVariantList()).toList();
|
||||
}
|
||||
|
||||
QVariantList EmojiModel::filterModel(const QString &filter, bool limit)
|
||||
@@ -105,7 +105,7 @@ void EmojiModel::emojiUsed(const QVariant &modelData)
|
||||
}
|
||||
|
||||
list.push_front(modelData);
|
||||
m_settings.setValue(QStringLiteral("Editor/emojis"), list);
|
||||
m_settings.setValue("Editor/emojis", list);
|
||||
|
||||
Q_EMIT historyChanged();
|
||||
}
|
||||
@@ -133,8 +133,8 @@ QVariantList EmojiModel::emojis(Category category) const
|
||||
|
||||
QVariantList EmojiModel::tones(const QString &baseEmoji) const
|
||||
{
|
||||
if (baseEmoji.endsWith(QStringLiteral("tone"))) {
|
||||
return EmojiTones::_tones.values(baseEmoji.split(QStringLiteral(":"))[0]);
|
||||
if (baseEmoji.endsWith("tone")) {
|
||||
return EmojiTones::_tones.values(baseEmoji.split(":")[0]);
|
||||
}
|
||||
return EmojiTones::_tones.values(baseEmoji);
|
||||
}
|
||||
@@ -145,54 +145,54 @@ QVariantList EmojiModel::categories() const
|
||||
{
|
||||
return QVariantList{
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::HistoryNoCustom},
|
||||
{QStringLiteral("name"), i18nc("Previously used emojis", "History")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("⌛️")},
|
||||
{"category", EmojiModel::HistoryNoCustom},
|
||||
{"name", i18nc("Previously used emojis", "History")},
|
||||
{"emoji", QStringLiteral("⌛️")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Smileys},
|
||||
{QStringLiteral("name"), i18nc("'Smileys' is a category of emoji", "Smileys")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("😏")},
|
||||
{"category", EmojiModel::Smileys},
|
||||
{"name", i18nc("'Smileys' is a category of emoji", "Smileys")},
|
||||
{"emoji", QStringLiteral("😏")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::People},
|
||||
{QStringLiteral("name"), i18nc("'People' is a category of emoji", "People")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("🙋♂️")},
|
||||
{"category", EmojiModel::People},
|
||||
{"name", i18nc("'People' is a category of emoji", "People")},
|
||||
{"emoji", QStringLiteral("🙋♂️")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Nature},
|
||||
{QStringLiteral("name"), i18nc("'Nature' is a category of emoji", "Nature")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("🌲")},
|
||||
{"category", EmojiModel::Nature},
|
||||
{"name", i18nc("'Nature' is a category of emoji", "Nature")},
|
||||
{"emoji", QStringLiteral("🌲")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Food},
|
||||
{QStringLiteral("name"), i18nc("'Food' is a category of emoji", "Food")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("🍛")},
|
||||
{"category", EmojiModel::Food},
|
||||
{"name", i18nc("'Food' is a category of emoji", "Food")},
|
||||
{"emoji", QStringLiteral("🍛")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Activities},
|
||||
{QStringLiteral("name"), i18nc("'Activities' is a category of emoji", "Activities")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("🚁")},
|
||||
{"category", EmojiModel::Activities},
|
||||
{"name", i18nc("'Activities' is a category of emoji", "Activities")},
|
||||
{"emoji", QStringLiteral("🚁")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Travel},
|
||||
{QStringLiteral("name"), i18nc("'Travel' is a category of emoji", "Travel")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("🚅")},
|
||||
{"category", EmojiModel::Travel},
|
||||
{"name", i18nc("'Travel' is a category of emoji", "Travel")},
|
||||
{"emoji", QStringLiteral("🚅")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Objects},
|
||||
{QStringLiteral("name"), i18nc("'Objects' is a category of emoji", "Objects")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("💡")},
|
||||
{"category", EmojiModel::Objects},
|
||||
{"name", i18nc("'Objects' is a category of emoji", "Objects")},
|
||||
{"emoji", QStringLiteral("💡")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Symbols},
|
||||
{QStringLiteral("name"), i18nc("'Symbols' is a category of emoji", "Symbols")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("🔣")},
|
||||
{"category", EmojiModel::Symbols},
|
||||
{"name", i18nc("'Symbols' is a category of emoji", "Symbols")},
|
||||
{"emoji", QStringLiteral("🔣")},
|
||||
}},
|
||||
{QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Flags},
|
||||
{QStringLiteral("name"), i18nc("'Flags' is a category of emoji", "Flags")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("🏁")},
|
||||
{"category", EmojiModel::Flags},
|
||||
{"name", i18nc("'Flags' is a category of emoji", "Flags")},
|
||||
{"emoji", QStringLiteral("🏁")},
|
||||
}},
|
||||
};
|
||||
}
|
||||
@@ -203,15 +203,15 @@ QVariantList EmojiModel::categoriesWithCustom() const
|
||||
cats.removeAt(0);
|
||||
cats.insert(0,
|
||||
QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::History},
|
||||
{QStringLiteral("name"), i18nc("Previously used emojis", "History")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("⌛️")},
|
||||
{"category", EmojiModel::History},
|
||||
{"name", i18nc("Previously used emojis", "History")},
|
||||
{"emoji", QStringLiteral("⌛️")},
|
||||
});
|
||||
cats.insert(1,
|
||||
QVariantMap{
|
||||
{QStringLiteral("category"), EmojiModel::Custom},
|
||||
{QStringLiteral("name"), i18nc("'Custom' is a category of emoji", "Custom")},
|
||||
{QStringLiteral("emoji"), QStringLiteral("🖼️")},
|
||||
{"category", EmojiModel::Custom},
|
||||
{"name", i18nc("'Custom' is a category of emoji", "Custom")},
|
||||
{"emoji", QStringLiteral("🖼️")},
|
||||
});
|
||||
;
|
||||
return cats;
|
||||
|
||||
@@ -33,7 +33,7 @@ struct Emoji {
|
||||
{
|
||||
arch >> object.unicode;
|
||||
arch >> object.shortName;
|
||||
object.isCustom = object.unicode.startsWith(QStringLiteral("image://"));
|
||||
object.isCustom = object.unicode.startsWith("image://");
|
||||
return arch;
|
||||
}
|
||||
|
||||
|
||||
@@ -82,9 +82,8 @@ void ImagePacksModel::reloadImages()
|
||||
// Load emoticons from the account data
|
||||
if (m_room->connection()->hasAccountData("im.ponies.user_emotes"_ls)) {
|
||||
auto json = m_room->connection()->accountData("im.ponies.user_emotes"_ls)->contentJson();
|
||||
json["pack"_ls] = QJsonObject{
|
||||
{"display_name"_ls,
|
||||
m_showStickers ? i18nc("As in 'The user's own Stickers'", "Own Stickers") : i18nc("As in 'The user's own emojis", "Own Emojis")},
|
||||
json["pack"] = QJsonObject{
|
||||
{"display_name", m_showStickers ? i18nc("As in 'The user's own Stickers'", "Own Stickers") : i18nc("As in 'The user's own emojis", "Own Emojis")},
|
||||
};
|
||||
const auto &content = ImagePackEventContent(json);
|
||||
if (!content.images.isEmpty()) {
|
||||
@@ -105,8 +104,8 @@ void ImagePacksModel::reloadImages()
|
||||
for (const auto &packKey : packs.keys()) {
|
||||
if (const auto &pack = stickerRoom->currentState().get<ImagePackEvent>(packKey)) {
|
||||
const auto packContent = pack->content();
|
||||
if ((!packContent.pack || !packContent.pack->usage || (packContent.pack->usage->contains("emoticon"_ls) && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker"_ls) && showStickers()))
|
||||
if ((!packContent.pack || !packContent.pack->usage || (packContent.pack->usage->contains("emoticon") && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker") && showStickers()))
|
||||
&& !packContent.images.isEmpty()) {
|
||||
m_events += packContent;
|
||||
}
|
||||
@@ -116,12 +115,12 @@ void ImagePacksModel::reloadImages()
|
||||
}
|
||||
|
||||
// Load emoticons from the current room
|
||||
auto events = m_room->currentState().eventsOfType("im.ponies.room_emotes"_ls);
|
||||
auto events = m_room->currentState().eventsOfType("im.ponies.room_emotes");
|
||||
for (const auto &event : events) {
|
||||
auto packContent = eventCast<const ImagePackEvent>(event)->content();
|
||||
if (packContent.pack.has_value()) {
|
||||
if (!packContent.pack->usage || (packContent.pack->usage->contains("emoticon"_ls) && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker"_ls) && showStickers())) {
|
||||
if (!packContent.pack->usage || (packContent.pack->usage->contains("emoticon") && showEmoticons())
|
||||
|| (packContent.pack->usage->contains("sticker") && showStickers())) {
|
||||
m_events += packContent;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,8 +8,6 @@
|
||||
|
||||
#include <QDebug>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
bool operator<(const LiveLocationData &lhs, const LiveLocationData &rhs)
|
||||
@@ -78,7 +76,7 @@ QVariant LiveLocationsModel::data(const QModelIndex &index, int roleName) const
|
||||
return longitude.toFloat();
|
||||
}
|
||||
case AssetRole:
|
||||
return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
||||
return data.beaconInfo["org.matrix.msc3488.asset"_ls].toObject()["type"].toString();
|
||||
case AuthorRole:
|
||||
return m_room->getUser(data.senderId);
|
||||
case IsLiveRole: {
|
||||
@@ -90,11 +88,6 @@ QVariant LiveLocationsModel::data(const QModelIndex &index, int roleName) const
|
||||
const auto timeout = data.beaconInfo.value("timeout"_ls).toDouble(600000);
|
||||
return lastTs + timeout >= QDateTime::currentDateTime().toMSecsSinceEpoch();
|
||||
}
|
||||
case HeadingRole: {
|
||||
bool success = false;
|
||||
const auto heading = data.beacon["org.matrix.msc3488.location"_ls].toObject()["org.kde.itinerary.heading"_ls].toString().toDouble(&success);
|
||||
return success ? heading : NAN;
|
||||
}
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -108,7 +101,6 @@ QHash<int, QByteArray> LiveLocationsModel::roleNames() const
|
||||
r.insert(AssetRole, "asset");
|
||||
r.insert(AuthorRole, "author");
|
||||
r.insert(IsLiveRole, "isLive");
|
||||
r.insert(HeadingRole, "heading");
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -129,7 +121,7 @@ QRectF LiveLocationsModel::boundingBox() const
|
||||
|
||||
void LiveLocationsModel::addEvent(const Quotient::RoomEvent *event)
|
||||
{
|
||||
if (event->isStateEvent() && event->matrixType() == "org.matrix.msc3672.beacon_info"_ls) {
|
||||
if (event->isStateEvent() && event->matrixType() == "org.matrix.msc3672.beacon_info") {
|
||||
LiveLocationData data;
|
||||
data.senderId = event->senderId();
|
||||
data.beaconInfo = event->contentJson();
|
||||
|
||||
@@ -49,7 +49,6 @@ public:
|
||||
AssetRole, /**< Type of location event, e.g. self pin of the user location. */
|
||||
AuthorRole, /**< The author of the event. */
|
||||
IsLiveRole, /**< Boolean that indicates whether a live location beacon is still live. */
|
||||
HeadingRole, /**< Heading in degree (not part of any MSC yet, using an Itinerary extension). */
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include "locationsmodel.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
LocationsModel::LocationsModel(QObject *parent)
|
||||
@@ -15,7 +13,7 @@ LocationsModel::LocationsModel(QObject *parent)
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_ls] == "m.location"_ls) {
|
||||
if (event->contentJson()["msgtype"] == "m.location") {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
}
|
||||
@@ -25,7 +23,7 @@ LocationsModel::LocationsModel(QObject *parent)
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_ls] == "m.location"_ls) {
|
||||
if (event->contentJson()["msgtype"] == "m.location") {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
}
|
||||
@@ -36,7 +34,7 @@ LocationsModel::LocationsModel(QObject *parent)
|
||||
if (!is<RoomMessageEvent>(*event)) {
|
||||
continue;
|
||||
}
|
||||
if (event->contentJson()["msgtype"_ls] == "m.location"_ls) {
|
||||
if (event->contentJson()["msgtype"] == "m.location") {
|
||||
const auto &e = *event;
|
||||
addLocation(eventCast<const RoomMessageEvent>(&e));
|
||||
}
|
||||
@@ -45,15 +43,11 @@ LocationsModel::LocationsModel(QObject *parent)
|
||||
});
|
||||
|
||||
connect(this, &LocationsModel::rowsInserted, this, &LocationsModel::boundingBoxChanged);
|
||||
|
||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole});
|
||||
});
|
||||
}
|
||||
|
||||
void LocationsModel::addLocation(const RoomMessageEvent *event)
|
||||
{
|
||||
const auto uri = event->contentJson()["org.matrix.msc3488.location"_ls]["uri"_ls].toString();
|
||||
const auto uri = event->contentJson()["org.matrix.msc3488.location"]["uri"].toString();
|
||||
const auto parts = uri.mid(4).split(QLatin1Char(','));
|
||||
if (parts.size() < 2) {
|
||||
qWarning() << "invalid geo: URI" << uri;
|
||||
@@ -67,7 +61,7 @@ void LocationsModel::addLocation(const RoomMessageEvent *event)
|
||||
.latitude = latitude,
|
||||
.longitude = longitude,
|
||||
.content = event->contentJson(),
|
||||
.author = m_room->user(event->senderId()),
|
||||
.author = dynamic_cast<NeoChatUser *>(m_room->user(event->senderId())),
|
||||
};
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <Quotient/events/roommessageevent.h>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
class LocationsModel : public QAbstractListModel
|
||||
{
|
||||
@@ -52,7 +51,7 @@ private:
|
||||
float latitude;
|
||||
float longitude;
|
||||
QJsonObject content;
|
||||
Quotient::User *author;
|
||||
NeoChatUser *author;
|
||||
};
|
||||
QList<LocationData> m_locations;
|
||||
void addLocation(const Quotient::RoomMessageEvent *event);
|
||||
|
||||
@@ -25,7 +25,7 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (role == SourceRole) {
|
||||
if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Image) {
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("source")].toUrl();
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()["source"].toUrl();
|
||||
} else if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Video) {
|
||||
auto progressInfo = mapToSource(index).data(MessageEventModel::ProgressInfoRole).value<Quotient::FileTransferInfo>();
|
||||
|
||||
@@ -39,7 +39,7 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
}
|
||||
if (role == TempSourceRole) {
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("tempInfo")].toMap()[QStringLiteral("source")].toUrl();
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()["tempInfo"].toMap()["source"].toUrl();
|
||||
}
|
||||
if (role == TypeRole) {
|
||||
if (mapToSource(index).data(MessageEventModel::DelegateTypeRole).toInt() == MessageEventModel::DelegateType::Image) {
|
||||
@@ -52,10 +52,10 @@ QVariant MediaMessageFilterModel::data(const QModelIndex &index, int role) const
|
||||
return mapToSource(index).data(Qt::DisplayRole);
|
||||
}
|
||||
if (role == SourceWidthRole) {
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("width")].toFloat();
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()["width"].toFloat();
|
||||
}
|
||||
if (role == SourceHeightRole) {
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()[QStringLiteral("height")].toFloat();
|
||||
return mapToSource(index).data(MessageEventModel::MediaInfoRole).toMap()["height"].toFloat();
|
||||
}
|
||||
|
||||
return sourceModel()->data(mapToSource(index), role);
|
||||
|
||||
@@ -20,12 +20,12 @@
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGuiApplication>
|
||||
#include <QMovie>
|
||||
#include <QTimeZone>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include "models/reactionmodel.h"
|
||||
#include "neochatuser.h"
|
||||
#include "texthandler.h"
|
||||
|
||||
using namespace Quotient;
|
||||
@@ -78,7 +78,7 @@ MessageEventModel::MessageEventModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyAuthor, ReadMarkersRole});
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReplyRole});
|
||||
});
|
||||
}
|
||||
|
||||
@@ -531,7 +531,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == AuthorRole) {
|
||||
auto author = isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId());
|
||||
auto author = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
|
||||
return m_currentRoom->getUser(author);
|
||||
}
|
||||
|
||||
@@ -579,7 +579,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
if (isPending) {
|
||||
// A pending event with an m.new_content key will be merged into the
|
||||
// original event so don't show.
|
||||
if (evt.contentJson().contains("m.new_content"_ls)) {
|
||||
if (evt.contentJson().contains("m.new_content")) {
|
||||
return EventStatus::Hidden;
|
||||
}
|
||||
return pendingIt->deliveryStatus();
|
||||
@@ -668,18 +668,18 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == IsReplyRole) {
|
||||
return !evt.contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString().isEmpty();
|
||||
return !evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
||||
}
|
||||
|
||||
if (role == ReplyIdRole) {
|
||||
return evt.contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
return evt.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
}
|
||||
|
||||
if (role == ReplyAuthor) {
|
||||
auto replyPtr = m_currentRoom->getReplyForEvent(evt);
|
||||
|
||||
if (replyPtr) {
|
||||
auto replyUser = m_currentRoom->user(replyPtr->senderId());
|
||||
auto replyUser = static_cast<NeoChatUser *>(m_currentRoom->user(replyPtr->senderId()));
|
||||
return m_currentRoom->getUser(replyUser);
|
||||
} else {
|
||||
return m_currentRoom->getUser(nullptr);
|
||||
@@ -733,8 +733,8 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
return QVariantMap{
|
||||
{"display"_ls, m_currentRoom->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"type"_ls, type},
|
||||
{"display", m_currentRoom->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"type", type},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -789,7 +789,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == AssetRole) {
|
||||
const auto assetType = evt.contentJson()["org.matrix.msc3488.asset"_ls].toObject()["type"_ls].toString();
|
||||
const auto assetType = evt.contentJson()["org.matrix.msc3488.asset"].toObject()["type"].toString();
|
||||
if (assetType.isEmpty()) {
|
||||
return {};
|
||||
}
|
||||
@@ -808,7 +808,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
QVariantList users;
|
||||
users.reserve(userIds.size());
|
||||
for (const auto &userId : userIds) {
|
||||
auto user = m_currentRoom->user(userId);
|
||||
auto user = static_cast<NeoChatUser *>(m_currentRoom->user(userId));
|
||||
users += m_currentRoom->getUser(user);
|
||||
}
|
||||
|
||||
@@ -820,7 +820,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
userIds.remove(m_currentRoom->localUser()->id());
|
||||
|
||||
if (userIds.count() > 5) {
|
||||
return QStringLiteral("+ %1").arg(userIds.count() - 5);
|
||||
return QStringLiteral("+ ") + QString::number(userIds.count() - 5);
|
||||
} else {
|
||||
return QString();
|
||||
}
|
||||
@@ -836,7 +836,7 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
*/
|
||||
QString readMarkersString = i18np("1 user: ", "%1 users: ", userIds.size());
|
||||
for (const auto &userId : userIds) {
|
||||
auto user = m_currentRoom->user(userId);
|
||||
auto user = static_cast<NeoChatUser *>(m_currentRoom->user(userId));
|
||||
readMarkersString += user->displayname(m_currentRoom) + i18nc("list separator", ", ");
|
||||
}
|
||||
readMarkersString.chop(2);
|
||||
@@ -866,28 +866,30 @@ QVariant MessageEventModel::data(const QModelIndex &idx, int role) const
|
||||
}
|
||||
|
||||
if (role == VerifiedRole) {
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
if (evt.originalEvent()) {
|
||||
auto encrypted = dynamic_cast<const EncryptedEvent *>(evt.originalEvent());
|
||||
Q_ASSERT(encrypted);
|
||||
return m_currentRoom->connection()->isVerifiedSession(encrypted->sessionId().toLatin1());
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
if (role == DisplayNameForInitialsRole) {
|
||||
auto user = isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId());
|
||||
auto user = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
|
||||
return user->displayname(m_currentRoom).remove(QStringLiteral(" (%1)").arg(user->id()));
|
||||
}
|
||||
|
||||
if (role == AuthorDisplayNameRole) {
|
||||
if (is<RoomMemberEvent>(evt) && !evt.unsignedJson()["prev_content"_ls]["displayname"_ls].isNull() && evt.stateKey() == evt.senderId()) {
|
||||
auto previousDisplayName = evt.unsignedJson()["prev_content"_ls]["displayname"_ls].toString().toHtmlEscaped();
|
||||
if (is<RoomMemberEvent>(evt) && !evt.unsignedJson()["prev_content"]["displayname"].isNull() && evt.stateKey() == evt.senderId()) {
|
||||
auto previousDisplayName = evt.unsignedJson()["prev_content"]["displayname"].toString().toHtmlEscaped();
|
||||
if (previousDisplayName.isEmpty()) {
|
||||
previousDisplayName = evt.senderId();
|
||||
}
|
||||
return previousDisplayName;
|
||||
} else {
|
||||
auto author = isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId());
|
||||
auto author = static_cast<NeoChatUser *>(isPending ? m_currentRoom->localUser() : m_currentRoom->user(evt.senderId()));
|
||||
return m_currentRoom->htmlSafeMemberName(author->id());
|
||||
}
|
||||
}
|
||||
@@ -943,79 +945,76 @@ QVariantMap MessageEventModel::getMediaInfoFromFileInfo(const EventContent::File
|
||||
|
||||
// Get the mxc URL for the media.
|
||||
if (!fileInfo->url().isValid() || eventId.isEmpty()) {
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
mediaInfo["source"] = QUrl();
|
||||
} else {
|
||||
QUrl source = m_currentRoom->makeMediaUrl(eventId, fileInfo->url());
|
||||
|
||||
if (source.isValid() && source.scheme() == QStringLiteral("mxc")) {
|
||||
mediaInfo["source"_ls] = source;
|
||||
mediaInfo["source"] = source;
|
||||
} else {
|
||||
mediaInfo["source"_ls] = QUrl();
|
||||
mediaInfo["source"] = QUrl();
|
||||
}
|
||||
}
|
||||
|
||||
auto mimeType = fileInfo->mimeType;
|
||||
// Add the MIME type for the media if available.
|
||||
mediaInfo["mimeType"_ls] = mimeType.name();
|
||||
mediaInfo["mimeType"] = mimeType.name();
|
||||
|
||||
// Add the MIME type icon if available.
|
||||
mediaInfo["mimeIcon"_ls] = mimeType.iconName();
|
||||
mediaInfo["mimeIcon"] = mimeType.iconName();
|
||||
|
||||
// Add media size if available.
|
||||
mediaInfo["size"_ls] = fileInfo->payloadSize;
|
||||
mediaInfo["size"] = fileInfo->payloadSize;
|
||||
|
||||
// Add parameter depending on media type.
|
||||
if (mimeType.name().contains(QStringLiteral("image"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::ImageContent *>(fileInfo)) {
|
||||
mediaInfo["width"_ls] = castInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = castInfo->imageSize.height();
|
||||
|
||||
// TODO: Images in certain formats (e.g. WebP) will be erroneously marked as animated, even if they are static.
|
||||
mediaInfo["animated"_ls] = QMovie::supportedFormats().contains(mimeType.preferredSuffix().toUtf8());
|
||||
mediaInfo["width"] = castInfo->imageSize.width();
|
||||
mediaInfo["height"] = castInfo->imageSize.height();
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
if (thumbnailInfo["source"].toUrl().scheme() == "mxc") {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"_ls].toString();
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"].toString();
|
||||
if (blurhash.isEmpty()) {
|
||||
tempInfo["source"_ls] = QUrl();
|
||||
tempInfo["source"] = QUrl();
|
||||
} else {
|
||||
tempInfo["source"_ls] = QUrl("image://blurhash/"_ls + blurhash);
|
||||
tempInfo["source"] = QUrl("image://blurhash/" + blurhash);
|
||||
}
|
||||
}
|
||||
mediaInfo["tempInfo"_ls] = tempInfo;
|
||||
mediaInfo["tempInfo"] = tempInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("video"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::VideoContent *>(fileInfo)) {
|
||||
mediaInfo["width"_ls] = castInfo->imageSize.width();
|
||||
mediaInfo["height"_ls] = castInfo->imageSize.height();
|
||||
mediaInfo["duration"_ls] = castInfo->duration;
|
||||
mediaInfo["width"] = castInfo->imageSize.width();
|
||||
mediaInfo["height"] = castInfo->imageSize.height();
|
||||
mediaInfo["duration"] = castInfo->duration;
|
||||
|
||||
if (!isThumbnail) {
|
||||
QVariantMap tempInfo;
|
||||
auto thumbnailInfo = getMediaInfoFromFileInfo(castInfo->thumbnailInfo(), eventId, true);
|
||||
if (thumbnailInfo["source"_ls].toUrl().scheme() == "mxc"_ls) {
|
||||
if (thumbnailInfo["source"].toUrl().scheme() == "mxc") {
|
||||
tempInfo = thumbnailInfo;
|
||||
} else {
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"_ls].toString();
|
||||
QString blurhash = castInfo->originalInfoJson["xyz.amorgan.blurhash"].toString();
|
||||
if (blurhash.isEmpty()) {
|
||||
tempInfo["source"_ls] = QUrl();
|
||||
tempInfo["source"] = QUrl();
|
||||
} else {
|
||||
tempInfo["source"_ls] = QUrl("image://blurhash/"_ls + blurhash);
|
||||
tempInfo["source"] = QUrl("image://blurhash/" + blurhash);
|
||||
}
|
||||
}
|
||||
mediaInfo["tempInfo"_ls] = tempInfo;
|
||||
mediaInfo["tempInfo"] = tempInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mimeType.name().contains(QStringLiteral("audio"))) {
|
||||
if (auto castInfo = static_cast<const EventContent::AudioContent *>(fileInfo)) {
|
||||
mediaInfo["duration"_ls] = castInfo->duration;
|
||||
mediaInfo["duration"] = castInfo->duration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1063,13 +1062,13 @@ void MessageEventModel::createReactionModelForEvent(const Quotient::RoomMessageE
|
||||
return;
|
||||
};
|
||||
|
||||
QMap<QString, QList<Quotient::User *>> reactions = {};
|
||||
QMap<QString, QList<NeoChatUser *>> reactions = {};
|
||||
for (const auto &a : annotations) {
|
||||
if (a->isRedacted()) { // Just in case?
|
||||
continue;
|
||||
}
|
||||
if (const auto &e = eventCast<const ReactionEvent>(a)) {
|
||||
reactions[e->key()].append(m_currentRoom->user(e->senderId()));
|
||||
reactions[e->key()].append(static_cast<NeoChatUser *>(m_currentRoom->user(e->senderId())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1096,7 +1095,7 @@ void MessageEventModel::createReactionModelForEvent(const Quotient::RoomMessageE
|
||||
if (m_reactionModels.contains(eventId)) {
|
||||
m_reactionModels[eventId]->setReactions(res);
|
||||
} else if (res.size() > 0) {
|
||||
m_reactionModels[eventId] = new ReactionModel(this, res, m_currentRoom->localUser());
|
||||
m_reactionModels[eventId] = new ReactionModel(this, res, static_cast<NeoChatUser *>(m_currentRoom->localUser()));
|
||||
} else {
|
||||
if (m_reactionModels.contains(eventId)) {
|
||||
delete m_reactionModels[eventId];
|
||||
|
||||
@@ -25,7 +25,7 @@ void PublicRoomListModel::setConnection(Connection *conn)
|
||||
|
||||
beginResetModel();
|
||||
|
||||
nextBatch = QString();
|
||||
nextBatch = "";
|
||||
attempted = false;
|
||||
rooms.clear();
|
||||
m_server.clear();
|
||||
@@ -41,7 +41,6 @@ void PublicRoomListModel::setConnection(Connection *conn)
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
if (m_connection) {
|
||||
@@ -68,17 +67,15 @@ void PublicRoomListModel::setServer(const QString &value)
|
||||
|
||||
beginResetModel();
|
||||
|
||||
nextBatch = QString();
|
||||
nextBatch = "";
|
||||
attempted = false;
|
||||
rooms.clear();
|
||||
Q_EMIT loadingChanged();
|
||||
|
||||
endResetModel();
|
||||
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
if (m_connection) {
|
||||
@@ -104,7 +101,7 @@ void PublicRoomListModel::setKeyword(const QString &value)
|
||||
|
||||
beginResetModel();
|
||||
|
||||
nextBatch = QString();
|
||||
nextBatch = "";
|
||||
attempted = false;
|
||||
rooms.clear();
|
||||
|
||||
@@ -113,7 +110,6 @@ void PublicRoomListModel::setKeyword(const QString &value)
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
}
|
||||
|
||||
if (m_connection) {
|
||||
@@ -137,7 +133,6 @@ void PublicRoomListModel::next(int count)
|
||||
}
|
||||
|
||||
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword, {}});
|
||||
Q_EMIT loadingChanged();
|
||||
|
||||
connect(job, &BaseJob::finished, this, [this] {
|
||||
attempted = true;
|
||||
@@ -155,7 +150,6 @@ void PublicRoomListModel::next(int count)
|
||||
}
|
||||
|
||||
this->job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -192,7 +186,7 @@ QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
|
||||
auto avatarUrl = room.avatarUrl;
|
||||
|
||||
if (avatarUrl.isEmpty()) {
|
||||
return {};
|
||||
return "";
|
||||
}
|
||||
return avatarUrl.url().remove(0, 6);
|
||||
}
|
||||
@@ -259,9 +253,4 @@ bool PublicRoomListModel::hasMore() const
|
||||
return !(attempted && nextBatch.isEmpty());
|
||||
}
|
||||
|
||||
bool PublicRoomListModel::loading() const
|
||||
{
|
||||
return job != nullptr;
|
||||
}
|
||||
|
||||
#include "moc_publicroomlistmodel.cpp"
|
||||
|
||||
@@ -48,11 +48,6 @@ class PublicRoomListModel : public QAbstractListModel
|
||||
*/
|
||||
Q_PROPERTY(bool hasMore READ hasMore NOTIFY hasMoreChanged)
|
||||
|
||||
/**
|
||||
* @biref Whether the model is still loading.
|
||||
*/
|
||||
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
@@ -103,8 +98,6 @@ public:
|
||||
|
||||
[[nodiscard]] bool hasMore() const;
|
||||
|
||||
[[nodiscard]] bool loading() const;
|
||||
|
||||
/**
|
||||
* @brief Load the next set of rooms.
|
||||
*
|
||||
@@ -118,7 +111,6 @@ private:
|
||||
QString m_keyword;
|
||||
|
||||
bool attempted = false;
|
||||
bool m_loading = false;
|
||||
QString nextBatch;
|
||||
|
||||
QVector<Quotient::PublicRoomsChunk> rooms;
|
||||
@@ -130,5 +122,4 @@ Q_SIGNALS:
|
||||
void serverChanged();
|
||||
void keywordChanged();
|
||||
void hasMoreChanged();
|
||||
void loadingChanged();
|
||||
};
|
||||
|
||||
@@ -69,20 +69,18 @@ PushRuleModel::PushRuleModel(QObject *parent)
|
||||
|
||||
void PushRuleModel::controllerConnectionChanged()
|
||||
{
|
||||
if (Controller::instance().activeConnection()) {
|
||||
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &PushRuleModel::updateNotificationRules);
|
||||
updateNotificationRules(QStringLiteral("m.push_rules"));
|
||||
}
|
||||
connect(Controller::instance().activeConnection(), &Quotient::Connection::accountDataChanged, this, &PushRuleModel::updateNotificationRules);
|
||||
updateNotificationRules("m.push_rules");
|
||||
}
|
||||
|
||||
void PushRuleModel::updateNotificationRules(const QString &type)
|
||||
{
|
||||
if (type != QStringLiteral("m.push_rules")) {
|
||||
if (type != "m.push_rules") {
|
||||
return;
|
||||
}
|
||||
|
||||
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson(QStringLiteral("m.push_rules"));
|
||||
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson[QStringLiteral("global")].toObject());
|
||||
const QJsonObject ruleDataJson = Controller::instance().activeConnection()->accountDataJson("m.push_rules");
|
||||
const Quotient::PushRuleset ruleData = Quotient::fromJson<Quotient::PushRuleset>(ruleDataJson["global"].toObject());
|
||||
|
||||
beginResetModel();
|
||||
m_rules.clear();
|
||||
@@ -203,7 +201,7 @@ bool PushRuleModel::globalNotificationsEnabled() const
|
||||
|
||||
void PushRuleModel::setGlobalNotificationsEnabled(bool enabled)
|
||||
{
|
||||
setNotificationRuleEnabled(QStringLiteral("override"), QStringLiteral(".m.rule.master"), !enabled);
|
||||
setNotificationRuleEnabled("override", ".m.rule.master", !enabled);
|
||||
}
|
||||
|
||||
bool PushRuleModel::globalNotificationsSet() const
|
||||
@@ -304,26 +302,26 @@ void PushRuleModel::addKeyword(const QString &keyword, const QString &roomId)
|
||||
kind = PushNotificationKind::Override;
|
||||
|
||||
Quotient::PushCondition roomCondition;
|
||||
roomCondition.kind = QStringLiteral("event_match");
|
||||
roomCondition.key = QStringLiteral("room_id");
|
||||
roomCondition.kind = "event_match";
|
||||
roomCondition.key = "room_id";
|
||||
roomCondition.pattern = roomId;
|
||||
pushConditions.append(roomCondition);
|
||||
|
||||
Quotient::PushCondition keywordCondition;
|
||||
keywordCondition.kind = QStringLiteral("event_match");
|
||||
keywordCondition.key = QStringLiteral("content.body");
|
||||
keywordCondition.kind = "event_match";
|
||||
keywordCondition.key = "content.body";
|
||||
keywordCondition.pattern = keyword;
|
||||
pushConditions.append(keywordCondition);
|
||||
}
|
||||
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleJob>(QLatin1String("global"),
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleJob>("global",
|
||||
PushNotificationKind::kindString(kind),
|
||||
keyword,
|
||||
actions,
|
||||
QString(),
|
||||
QString(),
|
||||
QLatin1String(""),
|
||||
QLatin1String(""),
|
||||
pushConditions,
|
||||
roomId.isEmpty() ? keyword : QString());
|
||||
roomId.isEmpty() ? keyword : QLatin1String(""));
|
||||
connect(job, &Quotient::BaseJob::failure, this, [job, keyword]() {
|
||||
qWarning() << QLatin1String("Unable to set push rule for keyword %1: ").arg(keyword) << job->errorString();
|
||||
});
|
||||
@@ -342,7 +340,7 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
}
|
||||
|
||||
auto kind = PushNotificationKind::kindString(m_rules[index].kind);
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>(QStringLiteral("global"), kind, m_rules[index].id);
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::DeletePushRuleJob>("global", kind, m_rules[index].id);
|
||||
connect(job, &Quotient::BaseJob::failure, this, [this, job, index]() {
|
||||
qWarning() << QLatin1String("Unable to remove push rule for keyword %1: ").arg(m_rules[index].id) << job->errorString();
|
||||
});
|
||||
@@ -350,10 +348,10 @@ void PushRuleModel::removeKeyword(const QString &keyword)
|
||||
|
||||
void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled)
|
||||
{
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::IsPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId);
|
||||
auto job = Controller::instance().activeConnection()->callApi<Quotient::IsPushRuleEnabledJob>("global", kind, ruleId);
|
||||
connect(job, &Quotient::BaseJob::success, this, [job, kind, ruleId, enabled]() {
|
||||
if (job->enabled() != enabled) {
|
||||
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleEnabledJob>(QStringLiteral("global"), kind, ruleId, enabled);
|
||||
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleEnabledJob>("global", kind, ruleId, enabled);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -361,13 +359,13 @@ void PushRuleModel::setNotificationRuleEnabled(const QString &kind, const QStrin
|
||||
void PushRuleModel::setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action)
|
||||
{
|
||||
QVector<QVariant> actions;
|
||||
if (ruleId == QStringLiteral(".m.rule.call")) {
|
||||
actions = actionToVariant(action, QStringLiteral("ring"));
|
||||
if (ruleId == ".m.rule.call") {
|
||||
actions = actionToVariant(action, "ring");
|
||||
} else {
|
||||
actions = actionToVariant(action);
|
||||
}
|
||||
|
||||
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleActionsJob>(QStringLiteral("global"), kind, ruleId, actions);
|
||||
Controller::instance().activeConnection()->callApi<Quotient::SetPushRuleActionsJob>("global", kind, ruleId, actions);
|
||||
}
|
||||
|
||||
PushNotificationAction::Action PushRuleModel::variantToAction(const QVector<QVariant> &actions, bool enabled)
|
||||
@@ -385,10 +383,10 @@ PushNotificationAction::Action PushRuleModel::variantToAction(const QVector<QVar
|
||||
}
|
||||
|
||||
QJsonObject action = i.toJsonObject();
|
||||
if (action[QStringLiteral("set_tweak")].toString() == QStringLiteral("sound")) {
|
||||
if (action["set_tweak"].toString() == "sound") {
|
||||
isNoisy = true;
|
||||
} else if (action[QStringLiteral("set_tweak")].toString() == QStringLiteral("highlight")) {
|
||||
if (action[QStringLiteral("value")].toString() != QStringLiteral("false")) {
|
||||
} else if (action["set_tweak"].toString() == "highlight") {
|
||||
if (action["value"].toString() != "false") {
|
||||
highlightEnabled = true;
|
||||
}
|
||||
}
|
||||
@@ -425,19 +423,19 @@ QVector<QVariant> PushRuleModel::actionToVariant(PushNotificationAction::Action
|
||||
QVector<QVariant> actions;
|
||||
|
||||
if (action != PushNotificationAction::Off) {
|
||||
actions.append(QStringLiteral("notify"));
|
||||
actions.append("notify");
|
||||
} else {
|
||||
actions.append(QStringLiteral("dont_notify"));
|
||||
actions.append("dont_notify");
|
||||
}
|
||||
if (action == PushNotificationAction::Noisy || action == PushNotificationAction::NoisyHighlight) {
|
||||
QJsonObject soundTweak;
|
||||
soundTweak.insert(QStringLiteral("set_tweak"), QStringLiteral("sound"));
|
||||
soundTweak.insert(QStringLiteral("value"), sound);
|
||||
soundTweak.insert("set_tweak", "sound");
|
||||
soundTweak.insert("value", sound);
|
||||
actions.append(soundTweak);
|
||||
}
|
||||
if (action == PushNotificationAction::Highlight || action == PushNotificationAction::NoisyHighlight) {
|
||||
QJsonObject highlightTweak;
|
||||
highlightTweak.insert(QStringLiteral("set_tweak"), QStringLiteral("highlight"));
|
||||
highlightTweak.insert("set_tweak", "highlight");
|
||||
actions.append(highlightTweak);
|
||||
}
|
||||
|
||||
|
||||
@@ -236,6 +236,6 @@ private:
|
||||
void setNotificationRuleEnabled(const QString &kind, const QString &ruleId, bool enabled);
|
||||
void setNotificationRuleActions(const QString &kind, const QString &ruleId, PushNotificationAction::Action action);
|
||||
PushNotificationAction::Action variantToAction(const QVector<QVariant> &actions, bool enabled);
|
||||
QVector<QVariant> actionToVariant(PushNotificationAction::Action action, const QString &sound = QStringLiteral("default"));
|
||||
QVector<QVariant> actionToVariant(PushNotificationAction::Action action, const QString &sound = "default");
|
||||
};
|
||||
Q_DECLARE_METATYPE(PushRuleModel *)
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <Quotient/user.h>
|
||||
#include "neochatuser.h"
|
||||
|
||||
ReactionModel::ReactionModel(QObject *parent, QList<Reaction> reactions, Quotient::User *localUser)
|
||||
ReactionModel::ReactionModel(QObject *parent, QList<Reaction> reactions, NeoChatUser *localUser)
|
||||
: QAbstractListModel(parent)
|
||||
, m_localUser(localUser)
|
||||
{
|
||||
@@ -31,7 +31,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
if (role == TextRole) {
|
||||
if (reaction.authors.count() > 1) {
|
||||
return QStringLiteral("%1 %2").arg(reaction.reaction, reaction.authors.count());
|
||||
return reaction.reaction + QStringLiteral(" %1").arg(reaction.authors.count());
|
||||
} else {
|
||||
return reaction.reaction;
|
||||
}
|
||||
@@ -52,7 +52,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
text += i18nc("Separate the usernames of users", " and ");
|
||||
}
|
||||
}
|
||||
text += reaction.authors.at(i).toMap()[QStringLiteral("displayName")].toString();
|
||||
text += reaction.authors.at(i).toMap()["displayName"].toString();
|
||||
}
|
||||
|
||||
if (reaction.authors.count() > 3) {
|
||||
@@ -74,7 +74,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
if (role == HasLocalUser) {
|
||||
for (auto author : reaction.authors) {
|
||||
if (author.toMap()[QStringLiteral("id")] == m_localUser->id()) {
|
||||
if (author.toMap()["id"] == m_localUser->id()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
|
||||
#include <QAbstractListModel>
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class User;
|
||||
}
|
||||
class NeoChatUser;
|
||||
|
||||
/**
|
||||
* @class ReactionModel
|
||||
@@ -39,7 +36,7 @@ public:
|
||||
HasLocalUser, /**< Whether the local user is in the list of authors. */
|
||||
};
|
||||
|
||||
explicit ReactionModel(QObject *parent = nullptr, QList<Reaction> reactions = {}, Quotient::User *localUser = nullptr);
|
||||
explicit ReactionModel(QObject *parent = nullptr, QList<Reaction> reactions = {}, NeoChatUser *localUser = nullptr);
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
@@ -70,6 +67,6 @@ public:
|
||||
private:
|
||||
QList<Reaction> m_reactions;
|
||||
|
||||
Quotient::User *m_localUser;
|
||||
NeoChatUser *m_localUser;
|
||||
};
|
||||
Q_DECLARE_METATYPE(ReactionModel *)
|
||||
|
||||
@@ -40,19 +40,19 @@ RoomListModel::RoomListModel(QObject *parent)
|
||||
#if QT_VERSION < QT_VERSION_CHECK(6, 6, 0)
|
||||
#ifndef Q_OS_ANDROID
|
||||
// copied from Telegram desktop
|
||||
const auto launcherUrl = "application://org.kde.neochat.desktop"_ls;
|
||||
const auto launcherUrl = "application://org.kde.neochat.desktop";
|
||||
// Gnome requires that count is a 64bit integer
|
||||
const qint64 counterSlice = std::min(m_notificationCount, 9999);
|
||||
QVariantMap dbusUnityProperties;
|
||||
|
||||
if (counterSlice > 0) {
|
||||
dbusUnityProperties["count"_ls] = counterSlice;
|
||||
dbusUnityProperties["count-visible"_ls] = true;
|
||||
dbusUnityProperties["count"] = counterSlice;
|
||||
dbusUnityProperties["count-visible"] = true;
|
||||
} else {
|
||||
dbusUnityProperties["count-visible"_ls] = false;
|
||||
dbusUnityProperties["count-visible"] = false;
|
||||
}
|
||||
|
||||
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat"_ls, "com.canonical.Unity.LauncherEntry"_ls, "Update"_ls);
|
||||
auto signal = QDBusMessage::createSignal("/com/canonical/unity/launcherentry/neochat", "com.canonical.Unity.LauncherEntry", "Update");
|
||||
|
||||
signal.setArguments({launcherUrl, dbusUnityProperties});
|
||||
|
||||
@@ -297,9 +297,9 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
return NeoChatRoomType::Normal;
|
||||
}
|
||||
QJsonObject contentJson = creationEvent->contentJson();
|
||||
QJsonObject::const_iterator typeIter = contentJson.find("type"_ls);
|
||||
QJsonObject::const_iterator typeIter = contentJson.find("type");
|
||||
if (typeIter != contentJson.end()) {
|
||||
if (typeIter.value().toString() == "m.space"_ls) {
|
||||
if (typeIter.value().toString() == "m.space") {
|
||||
return NeoChatRoomType::Space;
|
||||
}
|
||||
}
|
||||
@@ -400,7 +400,7 @@ QString RoomListModel::categoryName(int category)
|
||||
case NeoChatRoomType::Space:
|
||||
return i18n("Spaces");
|
||||
default:
|
||||
return {};
|
||||
return "Deadbeef";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ private:
|
||||
QMap<int, bool> m_categoryVisibility;
|
||||
|
||||
int m_notificationCount = 0;
|
||||
QString m_activeSpaceId;
|
||||
QString m_activeSpaceId = "";
|
||||
|
||||
void connectRoomSignals(NeoChatRoom *room);
|
||||
|
||||
|
||||
@@ -5,14 +5,13 @@
|
||||
|
||||
#include "messageeventmodel.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include "neochatuser.h"
|
||||
#include <Quotient/events/stickerevent.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
// TODO search only in the current room
|
||||
@@ -20,9 +19,6 @@ using namespace Quotient;
|
||||
SearchModel::SearchModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this] {
|
||||
Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {AuthorRole, ReadMarkersRole});
|
||||
});
|
||||
}
|
||||
|
||||
QString SearchModel::searchText() const
|
||||
@@ -57,7 +53,7 @@ void SearchModel::search()
|
||||
.searchTerm = m_searchText,
|
||||
.keys = {},
|
||||
.filter = filter,
|
||||
.orderBy = "recent"_ls,
|
||||
.orderBy = "recent",
|
||||
.eventContext = SearchJob::IncludeEventContext{3, 3, true},
|
||||
.includeState = false,
|
||||
.groupings = none,
|
||||
@@ -113,7 +109,7 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
return false;
|
||||
case ReplyAuthorRole:
|
||||
if (const auto &replyPtr = m_room->getReplyForEvent(event)) {
|
||||
return m_room->getUser(m_room->user(replyPtr->senderId()));
|
||||
return m_room->getUser(static_cast<NeoChatUser *>(m_room->user(replyPtr->senderId())));
|
||||
} else {
|
||||
return m_room->getUser(nullptr);
|
||||
}
|
||||
@@ -157,8 +153,8 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
|
||||
return QVariantMap{
|
||||
{"display"_ls, m_room->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"type"_ls, type},
|
||||
{"display", m_room->eventToString(*replyPtr, Qt::RichText)},
|
||||
{"type", type},
|
||||
};
|
||||
}
|
||||
case IsPendingRole:
|
||||
@@ -166,13 +162,13 @@ QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
case ShowLinkPreviewRole:
|
||||
return false;
|
||||
case IsReplyRole:
|
||||
return !event.contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString().isEmpty();
|
||||
return !event.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString().isEmpty();
|
||||
case HighlightRole:
|
||||
return !m_room->isDirectChat() && m_room->isEventHighlighted(&event);
|
||||
case EventIdRole:
|
||||
return event.id();
|
||||
case ReplyIdRole:
|
||||
return event.contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
return event.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
}
|
||||
return MessageEventModel::DelegateType::Message;
|
||||
}
|
||||
@@ -261,7 +257,7 @@ QString renderDate(const QDateTime ×tamp)
|
||||
return i18n("The day before yesterday");
|
||||
}
|
||||
if (date > QDate::currentDate().addDays(-7)) {
|
||||
return date.toString("dddd"_ls);
|
||||
return date.toString("dddd");
|
||||
}
|
||||
|
||||
return QLocale::system().toString(date, QLocale::ShortFormat);
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
ServerListModel::ServerListModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup serverGroup(&dataResource, QStringLiteral("Servers"));
|
||||
KConfig dataResource("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup serverGroup(&dataResource, "Servers");
|
||||
|
||||
QString domain = Controller::instance().activeConnection()->domain();
|
||||
|
||||
@@ -91,8 +91,8 @@ int ServerListModel::rowCount(const QModelIndex &parent) const
|
||||
|
||||
void ServerListModel::checkServer(const QString &url)
|
||||
{
|
||||
KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup serverGroup(&dataResource, QStringLiteral("Servers"));
|
||||
KConfig dataResource("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup serverGroup(&dataResource, "Servers");
|
||||
|
||||
if (!serverGroup.hasKey(url)) {
|
||||
if (Quotient::isJobPending(m_checkServerJob)) {
|
||||
@@ -108,8 +108,8 @@ void ServerListModel::checkServer(const QString &url)
|
||||
|
||||
void ServerListModel::addServer(const QString &url)
|
||||
{
|
||||
KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup serverGroup(&dataResource, QStringLiteral("Servers"));
|
||||
KConfig dataResource("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup serverGroup(&dataResource, "Servers");
|
||||
|
||||
if (!serverGroup.hasKey(url)) {
|
||||
Server newServer = Server{
|
||||
@@ -129,8 +129,8 @@ void ServerListModel::addServer(const QString &url)
|
||||
|
||||
void ServerListModel::removeServerAtIndex(int row)
|
||||
{
|
||||
KConfig dataResource(QStringLiteral("data"), KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup serverGroup(&dataResource, QStringLiteral("Servers"));
|
||||
KConfig dataResource("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup serverGroup(&dataResource, "Servers");
|
||||
serverGroup.deleteEntry(data(index(row), UrlRole).toString());
|
||||
|
||||
beginRemoveRows(QModelIndex(), row, row);
|
||||
|
||||
@@ -80,7 +80,7 @@ bool SortFilterRoomListModel::filterAcceptsRow(int source_row, const QModelIndex
|
||||
|
||||
bool acceptRoom =
|
||||
sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != QStringLiteral("upgraded")
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded"
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() == false;
|
||||
|
||||
if (m_activeSpaceId.isEmpty()) {
|
||||
|
||||
@@ -27,7 +27,7 @@ bool SortFilterSpaceListModel::filterAcceptsRow(int source_row, const QModelInde
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
return sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool()
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != QStringLiteral("upgraded")
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() != "upgraded"
|
||||
&& !sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsChildSpaceRole).toBool();
|
||||
}
|
||||
|
||||
|
||||
@@ -26,7 +26,8 @@ QVariant StateModel::data(const QModelIndex &index, int role) const
|
||||
|
||||
int StateModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
return !m_room || parent.isValid() ? 0 : m_room->currentState().events().size();
|
||||
Q_UNUSED(parent);
|
||||
return m_room->currentState().events().size();
|
||||
}
|
||||
|
||||
NeoChatRoom *StateModel::room() const
|
||||
@@ -36,12 +37,8 @@ NeoChatRoom *StateModel::room() const
|
||||
|
||||
void StateModel::setRoom(NeoChatRoom *room)
|
||||
{
|
||||
if (m_room == room) {
|
||||
return;
|
||||
}
|
||||
m_room = room;
|
||||
Q_EMIT roomChanged();
|
||||
|
||||
beginResetModel();
|
||||
m_stateEvents.clear();
|
||||
m_stateEvents = m_room->currentState().events().keys();
|
||||
|
||||
@@ -112,10 +112,10 @@ void StickerModel::postSticker(int index)
|
||||
const auto &body = image.body ? *image.body : QString();
|
||||
QJsonObject infoJson;
|
||||
if (image.info) {
|
||||
infoJson["w"_ls] = image.info->imageSize.width();
|
||||
infoJson["h"_ls] = image.info->imageSize.height();
|
||||
infoJson["mimetype"_ls] = image.info->mimeType.name();
|
||||
infoJson["size"_ls] = image.info->payloadSize;
|
||||
infoJson["w"] = image.info->imageSize.width();
|
||||
infoJson["h"] = image.info->imageSize.height();
|
||||
infoJson["mimetype"] = image.info->mimeType.name();
|
||||
infoJson["size"] = image.info->payloadSize;
|
||||
// TODO thumbnail
|
||||
}
|
||||
QJsonObject content{
|
||||
@@ -123,7 +123,7 @@ void StickerModel::postSticker(int index)
|
||||
{"url"_ls, image.url.toString()},
|
||||
{"info"_ls, infoJson},
|
||||
};
|
||||
m_room->postJson("m.sticker"_ls, content);
|
||||
m_room->postJson("m.sticker", content);
|
||||
}
|
||||
|
||||
#include "moc_stickermodel.cpp"
|
||||
|
||||
@@ -138,7 +138,7 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
|
||||
return displayName;
|
||||
}
|
||||
|
||||
return QStringLiteral("Unknown User");
|
||||
return "Unknown User";
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
auto avatarUrl = user.avatarUrl;
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
#include "userlistmodel.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/events/roompowerlevelsevent.h>
|
||||
|
||||
@@ -16,9 +14,6 @@ UserListModel::UserListModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_currentRoom(nullptr)
|
||||
{
|
||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, [this]() {
|
||||
refreshAllUsers();
|
||||
});
|
||||
}
|
||||
|
||||
void UserListModel::setRoom(NeoChatRoom *room)
|
||||
@@ -76,7 +71,8 @@ QVariant UserListModel::data(const QModelIndex &index, int role) const
|
||||
return user->id();
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
return m_currentRoom->avatarForMember(user);
|
||||
auto neoChatUser = static_cast<NeoChatUser *>(user);
|
||||
return m_currentRoom->avatarForMember(neoChatUser);
|
||||
}
|
||||
if (role == ObjectRole) {
|
||||
return QVariant::fromValue(user);
|
||||
@@ -163,19 +159,17 @@ void UserListModel::refreshAllUsers()
|
||||
}
|
||||
m_users.clear();
|
||||
|
||||
if (m_currentRoom != nullptr) {
|
||||
m_users = m_currentRoom->users();
|
||||
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
|
||||
m_users = m_currentRoom->users();
|
||||
std::sort(m_users.begin(), m_users.end(), m_currentRoom->memberSorter());
|
||||
|
||||
for (User *user : std::as_const(m_users)) {
|
||||
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
||||
refreshUser(user, {AvatarRole});
|
||||
});
|
||||
}
|
||||
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
||||
setRoom(nullptr);
|
||||
for (User *user : std::as_const(m_users)) {
|
||||
connect(user, &User::defaultAvatarChanged, this, [this, user]() {
|
||||
refreshUser(user, {AvatarRole});
|
||||
});
|
||||
}
|
||||
connect(m_currentRoom->connection(), &Connection::loggedOut, this, [this]() {
|
||||
setRoom(nullptr);
|
||||
});
|
||||
endResetModel();
|
||||
Q_EMIT usersRefreshed();
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ void KWebShortcutModel::setSelectedText(const QString &selectedText)
|
||||
}
|
||||
|
||||
QString searchText = selectedText;
|
||||
searchText = searchText.replace(QLatin1Char('\n'), QLatin1Char(' ')).replace(QLatin1Char('\r'), QLatin1Char(' ')).simplified();
|
||||
searchText = searchText.replace('\n', ' ').replace('\r', ' ').simplified();
|
||||
if (searchText.isEmpty()) {
|
||||
endResetModel();
|
||||
return;
|
||||
@@ -115,7 +115,7 @@ void KWebShortcutModel::trigger(const QString &data)
|
||||
#ifdef HAVE_KIO
|
||||
KUriFilterData filterData(data);
|
||||
if (KUriFilter::self()->filterSearchUri(filterData, KUriFilter::WebShortcutFilter)) {
|
||||
Q_EMIT openUrl(filterData.uri());
|
||||
Q_EMIT openUrl(filterData.uri().url());
|
||||
}
|
||||
#else
|
||||
Q_UNUSED(data);
|
||||
@@ -125,7 +125,7 @@ void KWebShortcutModel::trigger(const QString &data)
|
||||
void KWebShortcutModel::configureWebShortcuts()
|
||||
{
|
||||
#ifdef HAVE_KIO
|
||||
auto job = new KIO::CommandLauncherJob(QStringLiteral("kcmshell5"), QStringList() << QStringLiteral("webshortcuts"), this);
|
||||
auto job = new KIO::CommandLauncherJob("kcmshell5", QStringList() << "webshortcuts", this);
|
||||
job->exec();
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -4,10 +4,8 @@
|
||||
#include "neochatroom.h"
|
||||
|
||||
#include <QFileInfo>
|
||||
#include <QGuiApplication>
|
||||
#include <QMetaObject>
|
||||
#include <QMimeDatabase>
|
||||
#include <QPalette>
|
||||
#include <QTemporaryFile>
|
||||
#include <QTextDocument>
|
||||
|
||||
@@ -15,7 +13,6 @@
|
||||
#include <QMediaPlayer>
|
||||
|
||||
#include <Quotient/jobs/basejob.h>
|
||||
#include <Quotient/user.h>
|
||||
#include <qcoro/qcorosignal.h>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
@@ -46,7 +43,6 @@
|
||||
#include "neochatconfig.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "texthandler.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <KConfig>
|
||||
#include <KConfigGroup>
|
||||
@@ -70,8 +66,8 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
connect(this, &Room::aboutToAddHistoricalMessages, this, &NeoChatRoom::readMarkerLoadedChanged);
|
||||
|
||||
// Load cached event if available.
|
||||
KConfig dataResource("data"_ls, KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup eventCacheGroup(&dataResource, "EventCache"_ls);
|
||||
KConfig dataResource("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup eventCacheGroup(&dataResource, "EventCache");
|
||||
|
||||
if (eventCacheGroup.hasKey(id())) {
|
||||
auto eventJson = QJsonDocument::fromJson(eventCacheGroup.readEntry(id(), QByteArray())).object();
|
||||
@@ -121,7 +117,7 @@ NeoChatRoom::NeoChatRoom(Connection *connection, QString roomId, JoinState joinS
|
||||
Q_EMIT defaultUrlPreviewStateChanged();
|
||||
});
|
||||
connect(this, &Room::accountDataChanged, this, [this](QString type) {
|
||||
if (type == "org.matrix.room.preview_urls"_ls) {
|
||||
if (type == "org.matrix.room.preview_urls") {
|
||||
Q_EMIT urlPreviewEnabledChanged();
|
||||
}
|
||||
});
|
||||
@@ -167,15 +163,15 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body)
|
||||
}
|
||||
|
||||
auto mime = QMimeDatabase().mimeTypeForUrl(url);
|
||||
url.setScheme("file"_ls);
|
||||
url.setScheme("file");
|
||||
QFileInfo fileInfo(url.isLocalFile() ? url.toLocalFile() : url.toString());
|
||||
EventContent::TypedBase *content;
|
||||
if (mime.name().startsWith("image/"_ls)) {
|
||||
if (mime.name().startsWith("image/")) {
|
||||
QImage image(url.toLocalFile());
|
||||
content = new EventContent::ImageContent(url, fileInfo.size(), mime, image.size(), fileInfo.fileName());
|
||||
} else if (mime.name().startsWith("audio/"_ls)) {
|
||||
} else if (mime.name().startsWith("audio/")) {
|
||||
content = new EventContent::AudioContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
||||
} else if (mime.name().startsWith("video/"_ls)) {
|
||||
} else if (mime.name().startsWith("video/")) {
|
||||
QMediaPlayer player;
|
||||
#if QT_VERSION > QT_VERSION_CHECK(6, 0, 0)
|
||||
player.setSource(url);
|
||||
@@ -238,10 +234,10 @@ QVariantList NeoChatRoom::getUsersTyping() const
|
||||
QVariantList userVariants;
|
||||
for (User *user : users) {
|
||||
userVariants.append(QVariantMap{
|
||||
{"id"_ls, user->id()},
|
||||
{"avatarMediaId"_ls, user->avatarMediaId(this)},
|
||||
{"displayName"_ls, user->displayname(this)},
|
||||
{"display"_ls, user->name()},
|
||||
{"id", user->id()},
|
||||
{"avatarMediaId", user->avatarMediaId(this)},
|
||||
{"displayName", user->displayname(this)},
|
||||
{"display", user->name()},
|
||||
});
|
||||
}
|
||||
return userVariants;
|
||||
@@ -314,8 +310,8 @@ void NeoChatRoom::cacheLastEvent()
|
||||
{
|
||||
auto event = lastEvent();
|
||||
if (event != nullptr) {
|
||||
KConfig dataResource("data"_ls, KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup eventCacheGroup(&dataResource, "EventCache"_ls);
|
||||
KConfig dataResource("data", KConfig::SimpleConfig, QStandardPaths::AppDataLocation);
|
||||
KConfigGroup eventCacheGroup(&dataResource, "EventCache");
|
||||
|
||||
auto eventJson = QJsonDocument(event->fullJson()).toJson();
|
||||
eventCacheGroup.writeEntry(id(), eventJson);
|
||||
@@ -332,9 +328,9 @@ bool NeoChatRoom::lastEventIsSpoiler() const
|
||||
{
|
||||
if (auto event = lastEvent()) {
|
||||
if (auto e = eventCast<const RoomMessageEvent>(event)) {
|
||||
if (e->hasTextContent() && e->content() && e->mimeType().name() == "text/html"_ls) {
|
||||
if (e->hasTextContent() && e->content() && e->mimeType().name() == "text/html") {
|
||||
auto htmlBody = static_cast<const Quotient::EventContent::TextContent *>(e->content())->body;
|
||||
return htmlBody.contains("data-mx-spoiler"_ls);
|
||||
return htmlBody.contains("data-mx-spoiler");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -416,11 +412,11 @@ QVariantList NeoChatRoom::getUsers(const QString &keyword, int limit) const
|
||||
int count = 0;
|
||||
for (const auto u : userList) {
|
||||
if (u->displayname(this).contains(keyword, Qt::CaseInsensitive)) {
|
||||
Quotient::User user(u->id(), u->connection());
|
||||
NeoChatUser user(u->id(), u->connection());
|
||||
QVariantMap userVariant{{QStringLiteral("id"), user.id()},
|
||||
{QStringLiteral("displayName"), user.displayname(this)},
|
||||
{QStringLiteral("avatarMediaId"), user.avatarMediaId(this)},
|
||||
{QStringLiteral("color"), Utils::getUserColor(user.hueF())}};
|
||||
{QStringLiteral("color"), user.color()}};
|
||||
|
||||
matchedList.append(QVariant::fromValue(userVariant));
|
||||
count++;
|
||||
@@ -435,21 +431,22 @@ QVariantList NeoChatRoom::getUsers(const QString &keyword, int limit) const
|
||||
|
||||
// An empty user is useful for returning as a model value to avoid properties being undefined.
|
||||
static const QVariantMap emptyUser = {
|
||||
{"isLocalUser"_ls, false},
|
||||
{"id"_ls, QString()},
|
||||
{"displayName"_ls, QString()},
|
||||
{"avatarSource"_ls, QUrl()},
|
||||
{"avatarMediaId"_ls, QString()},
|
||||
{"color"_ls, QColor()},
|
||||
{"object"_ls, QVariant()},
|
||||
{"isLocalUser", false},
|
||||
{"id", QString()},
|
||||
{"displayName", QString()},
|
||||
{"avatarSource", QUrl()},
|
||||
{"avatarMediaId", QString()},
|
||||
{"color", QColor()},
|
||||
{"object", QVariant()},
|
||||
};
|
||||
|
||||
QVariantMap NeoChatRoom::getUser(const QString &userID) const
|
||||
{
|
||||
return getUser(user(userID));
|
||||
NeoChatUser *userObject = static_cast<NeoChatUser *>(user(userID));
|
||||
return getUser(userObject);
|
||||
}
|
||||
|
||||
QVariantMap NeoChatRoom::getUser(User *user) const
|
||||
QVariantMap NeoChatRoom::getUser(NeoChatUser *user) const
|
||||
{
|
||||
if (user == nullptr) {
|
||||
return emptyUser;
|
||||
@@ -461,7 +458,7 @@ QVariantMap NeoChatRoom::getUser(User *user) const
|
||||
{QStringLiteral("displayName"), user->displayname(this)},
|
||||
{QStringLiteral("avatarSource"), avatarForMember(user)},
|
||||
{QStringLiteral("avatarMediaId"), user->avatarMediaId(this)},
|
||||
{QStringLiteral("color"), Utils::getUserColor(user->hueF())},
|
||||
{QStringLiteral("color"), user->color()},
|
||||
{QStringLiteral("object"), QVariant::fromValue(user)},
|
||||
};
|
||||
}
|
||||
@@ -500,7 +497,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
if (fileCaption.isEmpty()) {
|
||||
fileCaption = e.plainBody();
|
||||
} else if (e.content()->fileInfo()->originalName != e.plainBody()) {
|
||||
fileCaption = e.plainBody() + " | "_ls + fileCaption;
|
||||
fileCaption = e.plainBody() + " | " + fileCaption;
|
||||
}
|
||||
textHandler.setData(fileCaption);
|
||||
return !fileCaption.isEmpty() ? textHandler.handleRecievePlainText(Qt::PlainText, stripNewlines) : i18n("a file");
|
||||
@@ -516,7 +513,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
textHandler.setData(body);
|
||||
|
||||
Qt::TextFormat inputFormat;
|
||||
if (e.mimeType().name() == "text/plain"_ls) {
|
||||
if (e.mimeType().name() == "text/plain") {
|
||||
inputFormat = Qt::PlainText;
|
||||
} else {
|
||||
inputFormat = Qt::RichText;
|
||||
@@ -542,7 +539,7 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
|
||||
if (prettyPrint) {
|
||||
subjectName = QStringLiteral("<a href=\"https://matrix.to/#/%1\" style=\"color: %2\">%3</a>")
|
||||
.arg(e.userId(), Utils::getUserColor(user(e.userId())->hueF()).name(), subjectName);
|
||||
.arg(e.userId(), static_cast<NeoChatUser *>(user(e.userId()))->color().name(), subjectName);
|
||||
}
|
||||
|
||||
// The below code assumes senderName output in AuthorRole
|
||||
@@ -644,8 +641,8 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
return i18n("activated End-to-End Encryption");
|
||||
},
|
||||
[](const RoomCreateEvent &e) {
|
||||
return e.isUpgrade() ? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped())
|
||||
: i18n("created the room, version %1", e.version().isEmpty() ? "1"_ls : e.version().toHtmlEscaped());
|
||||
return e.isUpgrade() ? i18n("upgraded the room to version %1", e.version().isEmpty() ? "1" : e.version().toHtmlEscaped())
|
||||
: i18n("created the room, version %1", e.version().isEmpty() ? "1" : e.version().toHtmlEscaped());
|
||||
},
|
||||
[](const RoomPowerLevelsEvent &) {
|
||||
return i18nc("'power level' means permission level", "changed the power levels for this room");
|
||||
@@ -655,13 +652,13 @@ QString NeoChatRoom::eventToString(const RoomEvent &evt, Qt::TextFormat format,
|
||||
return i18n("changed the server access control lists for this room");
|
||||
}
|
||||
if (e.matrixType() == QLatin1String("im.vector.modular.widgets")) {
|
||||
if (e.fullJson()["unsigned"_ls]["prev_content"_ls].toObject().isEmpty()) {
|
||||
return i18nc("[User] added <name> widget", "added %1 widget", e.contentJson()["name"_ls].toString());
|
||||
if (e.fullJson()["unsigned"]["prev_content"].toObject().isEmpty()) {
|
||||
return i18nc("[User] added <name> widget", "added %1 widget", e.contentJson()["name"].toString());
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
return i18nc("[User] removed <name> widget", "removed %1 widget", e.fullJson()["unsigned"_ls]["prev_content"_ls]["name"_ls].toString());
|
||||
return i18nc("[User] removed <name> widget", "removed %1 widget", e.fullJson()["unsigned"]["prev_content"]["name"].toString());
|
||||
}
|
||||
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"_ls].toString());
|
||||
return i18nc("[User] configured <name> widget", "configured %1 widget", e.contentJson()["name"].toString());
|
||||
}
|
||||
if (e.matrixType() == "org.matrix.msc3672.beacon_info"_ls) {
|
||||
return e.contentJson()["description"_ls].toString();
|
||||
@@ -778,7 +775,7 @@ QString NeoChatRoom::eventToGenericString(const RoomEvent &evt) const
|
||||
return i18n("changed the server access control lists for this room");
|
||||
}
|
||||
if (e.matrixType() == QLatin1String("im.vector.modular.widgets")) {
|
||||
if (e.fullJson()["unsigned"_ls]["prev_content"_ls].toObject().isEmpty()) {
|
||||
if (e.fullJson()["unsigned"]["prev_content"].toObject().isEmpty()) {
|
||||
return i18n("added a widget");
|
||||
}
|
||||
if (e.contentJson().isEmpty()) {
|
||||
@@ -800,7 +797,7 @@ void NeoChatRoom::changeAvatar(const QUrl &localFile)
|
||||
const auto job = connection()->uploadFile(localFile.toLocalFile());
|
||||
if (isJobPending(job)) {
|
||||
connect(job, &BaseJob::success, this, [this, job] {
|
||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar"_ls, QString(), QJsonObject{{"url"_ls, job->contentUri().toString()}});
|
||||
connection()->callApi<SetRoomStateWithKeyJob>(id(), "m.room.avatar", QString(), QJsonObject{{"url", job->contentUri().toString()}});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -809,23 +806,23 @@ QString msgTypeToString(MessageEventType msgType)
|
||||
{
|
||||
switch (msgType) {
|
||||
case MessageEventType::Text:
|
||||
return "m.text"_ls;
|
||||
return "m.text";
|
||||
case MessageEventType::File:
|
||||
return "m.file"_ls;
|
||||
return "m.file";
|
||||
case MessageEventType::Audio:
|
||||
return "m.audio"_ls;
|
||||
return "m.audio";
|
||||
case MessageEventType::Emote:
|
||||
return "m.emote"_ls;
|
||||
return "m.emote";
|
||||
case MessageEventType::Image:
|
||||
return "m.image"_ls;
|
||||
return "m.image";
|
||||
case MessageEventType::Video:
|
||||
return "m.video"_ls;
|
||||
return "m.video";
|
||||
case MessageEventType::Notice:
|
||||
return "m.notice"_ls;
|
||||
return "m.notice";
|
||||
case MessageEventType::Location:
|
||||
return "m.location"_ls;
|
||||
return "m.location";
|
||||
default:
|
||||
return "m.text"_ls;
|
||||
return "m.text";
|
||||
}
|
||||
}
|
||||
|
||||
@@ -845,16 +842,15 @@ void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, Mess
|
||||
|
||||
if (isEdit) {
|
||||
QJsonObject json{
|
||||
{"type"_ls, "m.room.message"_ls},
|
||||
{"msgtype"_ls, msgTypeToString(type)},
|
||||
{"body"_ls, "* %1"_ls.arg(text)},
|
||||
{"format"_ls, "org.matrix.custom.html"_ls},
|
||||
{"formatted_body"_ls, html},
|
||||
{"m.new_content"_ls,
|
||||
QJsonObject{{"body"_ls, text}, {"msgtype"_ls, msgTypeToString(type)}, {"format"_ls, "org.matrix.custom.html"_ls}, {"formatted_body"_ls, html}}},
|
||||
{"m.relates_to"_ls, QJsonObject{{"rel_type"_ls, "m.replace"_ls}, {"event_id"_ls, relateToEventId}}}};
|
||||
{"type", "m.room.message"},
|
||||
{"msgtype", msgTypeToString(type)},
|
||||
{"body", "* " + text},
|
||||
{"format", "org.matrix.custom.html"},
|
||||
{"formatted_body", html},
|
||||
{"m.new_content", QJsonObject{{"body", text}, {"msgtype", msgTypeToString(type)}, {"format", "org.matrix.custom.html"}, {"formatted_body", html}}},
|
||||
{"m.relates_to", QJsonObject{{"rel_type", "m.replace"}, {"event_id", relateToEventId}}}};
|
||||
|
||||
postJson("m.room.message"_ls, json);
|
||||
postJson("m.room.message", json);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -863,25 +859,31 @@ void NeoChatRoom::postHtmlMessage(const QString &text, const QString &html, Mess
|
||||
|
||||
// clang-format off
|
||||
QJsonObject json{
|
||||
{"msgtype"_ls, msgTypeToString(type)},
|
||||
{"body"_ls, "> <%1> %2\n\n%3"_ls.arg(replyEvt.senderId(), eventToString(replyEvt), text)},
|
||||
{"format"_ls, "org.matrix.custom.html"_ls},
|
||||
{"m.relates_to"_ls,
|
||||
{"msgtype", msgTypeToString(type)},
|
||||
{"body", "> <" + replyEvt.senderId() + "> " + eventToString(replyEvt) + "\n\n" + text},
|
||||
{"format", "org.matrix.custom.html"},
|
||||
{"m.relates_to",
|
||||
QJsonObject {
|
||||
{"m.in_reply_to"_ls,
|
||||
{"m.in_reply_to",
|
||||
QJsonObject {
|
||||
{"event_id"_ls, replyEventId}
|
||||
{"event_id", replyEventId}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{"formatted_body"_ls,
|
||||
"<mx-reply><blockquote><a href=\"https://matrix.to/#/%1/%2\">In reply to</a> <a href=\"https://matrix.to/#/%3\">%4</a><br>%5</blockquote></mx-reply>%6"_ls.arg(id(), replyEventId, replyEvt.senderId(), replyEvt.senderId(), eventToString(replyEvt, Qt::RichText), html)
|
||||
{"formatted_body",
|
||||
"<mx-reply><blockquote><a href=\"https://matrix.to/#/" +
|
||||
id() + "/" +
|
||||
replyEventId +
|
||||
"\">In reply to</a> <a href=\"https://matrix.to/#/" +
|
||||
replyEvt.senderId() + "\">" + replyEvt.senderId() +
|
||||
"</a><br>" + eventToString(replyEvt, Qt::RichText) +
|
||||
"</blockquote></mx-reply>" + html
|
||||
}
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
postJson("m.room.message"_ls, json);
|
||||
postJson("m.room.message", json);
|
||||
|
||||
return;
|
||||
}
|
||||
@@ -999,37 +1001,37 @@ QString NeoChatRoom::joinRule() const
|
||||
|
||||
void NeoChatRoom::setJoinRule(const QString &joinRule)
|
||||
{
|
||||
if (!canSendState("m.room.join_rules"_ls)) {
|
||||
if (!canSendState("m.room.join_rules")) {
|
||||
qWarning() << "Power level too low to set join rules";
|
||||
return;
|
||||
}
|
||||
setState("m.room.join_rules"_ls, {}, QJsonObject{{"join_rule"_ls, joinRule}});
|
||||
setState("m.room.join_rules", "", QJsonObject{{"join_rule", joinRule}});
|
||||
// Not emitting joinRuleChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
||||
}
|
||||
|
||||
QString NeoChatRoom::historyVisibility() const
|
||||
{
|
||||
return currentState().get("m.room.history_visibility"_ls)->contentJson()["history_visibility"_ls].toString();
|
||||
return currentState().get("m.room.history_visibility")->contentJson()["history_visibility"_ls].toString();
|
||||
}
|
||||
|
||||
void NeoChatRoom::setHistoryVisibility(const QString &historyVisibilityRule)
|
||||
{
|
||||
if (!canSendState("m.room.history_visibility"_ls)) {
|
||||
if (!canSendState("m.room.history_visibility")) {
|
||||
qWarning() << "Power level too low to set history visibility";
|
||||
return;
|
||||
}
|
||||
|
||||
setState("m.room.history_visibility"_ls, {}, QJsonObject{{"history_visibility"_ls, historyVisibilityRule}});
|
||||
setState("m.room.history_visibility", "", QJsonObject{{"history_visibility", historyVisibilityRule}});
|
||||
// Not emitting historyVisibilityChanged() here, since that would override the change in the UI with the *current* value, which is not the *new* value.
|
||||
}
|
||||
|
||||
bool NeoChatRoom::defaultUrlPreviewState() const
|
||||
{
|
||||
auto urlPreviewsDisabled = currentState().get("org.matrix.room.preview_urls"_ls);
|
||||
auto urlPreviewsDisabled = currentState().get("org.matrix.room.preview_urls");
|
||||
|
||||
// Some rooms will not have this state event set so check for a nullptr return.
|
||||
if (urlPreviewsDisabled != nullptr) {
|
||||
return !urlPreviewsDisabled->contentJson()["disable"_ls].toBool();
|
||||
return !urlPreviewsDisabled->contentJson()["disable"].toBool();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -1037,7 +1039,7 @@ bool NeoChatRoom::defaultUrlPreviewState() const
|
||||
|
||||
void NeoChatRoom::setDefaultUrlPreviewState(const bool &defaultUrlPreviewState)
|
||||
{
|
||||
if (!canSendState("org.matrix.room.preview_urls"_ls)) {
|
||||
if (!canSendState("org.matrix.room.preview_urls")) {
|
||||
qWarning() << "Power level too low to set the default URL preview state for the room";
|
||||
return;
|
||||
}
|
||||
@@ -1071,13 +1073,13 @@ void NeoChatRoom::setDefaultUrlPreviewState(const bool &defaultUrlPreviewState)
|
||||
*
|
||||
* You just have to set disable to true to disable URL previews by default.
|
||||
*/
|
||||
setState("org.matrix.room.preview_urls"_ls, {}, QJsonObject{{"disable"_ls, !defaultUrlPreviewState}});
|
||||
setState("org.matrix.room.preview_urls", "", QJsonObject{{"disable", !defaultUrlPreviewState}});
|
||||
}
|
||||
|
||||
bool NeoChatRoom::urlPreviewEnabled() const
|
||||
{
|
||||
if (hasAccountData("org.matrix.room.preview_urls"_ls)) {
|
||||
return !accountData("org.matrix.room.preview_urls"_ls)->contentJson()["disable"_ls].toBool();
|
||||
if (hasAccountData("org.matrix.room.preview_urls")) {
|
||||
return !accountData("org.matrix.room.preview_urls")->contentJson()["disable"].toBool();
|
||||
} else {
|
||||
return defaultUrlPreviewState();
|
||||
}
|
||||
@@ -1096,10 +1098,7 @@ void NeoChatRoom::setUrlPreviewEnabled(const bool &urlPreviewEnabled)
|
||||
* "type": "org.matrix.room.preview_urls",
|
||||
* }
|
||||
*/
|
||||
connection()->callApi<SetAccountDataPerRoomJob>(localUser()->id(),
|
||||
id(),
|
||||
"org.matrix.room.preview_urls"_ls,
|
||||
QJsonObject{{"disable"_ls, !urlPreviewEnabled}});
|
||||
connection()->callApi<SetAccountDataPerRoomJob>(localUser()->id(), id(), "org.matrix.room.preview_urls", QJsonObject{{"disable", !urlPreviewEnabled}});
|
||||
}
|
||||
|
||||
void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel)
|
||||
@@ -1108,7 +1107,7 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
|
||||
qWarning() << "Cannot modify the power level of the only user";
|
||||
return;
|
||||
}
|
||||
if (!canSendState("m.room.power_levels"_ls)) {
|
||||
if (!canSendState("m.room.power_levels")) {
|
||||
qWarning() << "Power level too low to set user power levels";
|
||||
return;
|
||||
}
|
||||
@@ -1118,14 +1117,14 @@ void NeoChatRoom::setUserPowerLevel(const QString &userID, const int &powerLevel
|
||||
}
|
||||
int clampPowerLevel = std::clamp(powerLevel, 0, 100);
|
||||
|
||||
auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson();
|
||||
auto powerLevelUserOverrides = powerLevelContent["users"_ls].toObject();
|
||||
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
|
||||
auto powerLevelUserOverrides = powerLevelContent["users"].toObject();
|
||||
|
||||
if (powerLevelUserOverrides[userID] != clampPowerLevel) {
|
||||
powerLevelUserOverrides[userID] = clampPowerLevel;
|
||||
powerLevelContent["users"_ls] = powerLevelUserOverrides;
|
||||
powerLevelContent["users"] = powerLevelUserOverrides;
|
||||
|
||||
setState("m.room.power_levels"_ls, {}, powerLevelContent);
|
||||
setState("m.room.power_levels", "", powerLevelContent);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1141,19 +1140,19 @@ int NeoChatRoom::getUserPowerLevel(const QString &userId) const
|
||||
int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent) const
|
||||
{
|
||||
const auto powerLevelEvent = currentState().get<RoomPowerLevelsEvent>();
|
||||
if (eventName == "ban"_ls) {
|
||||
if (eventName == "ban") {
|
||||
return powerLevelEvent->ban();
|
||||
} else if (eventName == "kick"_ls) {
|
||||
} else if (eventName == "kick") {
|
||||
return powerLevelEvent->kick();
|
||||
} else if (eventName == "invite"_ls) {
|
||||
} else if (eventName == "invite") {
|
||||
return powerLevelEvent->invite();
|
||||
} else if (eventName == "redact"_ls) {
|
||||
} else if (eventName == "redact") {
|
||||
return powerLevelEvent->redact();
|
||||
} else if (eventName == "users_default"_ls) {
|
||||
} else if (eventName == "users_default") {
|
||||
return powerLevelEvent->usersDefault();
|
||||
} else if (eventName == "state_default"_ls) {
|
||||
} else if (eventName == "state_default") {
|
||||
return powerLevelEvent->stateDefault();
|
||||
} else if (eventName == "events_default"_ls) {
|
||||
} else if (eventName == "events_default") {
|
||||
return powerLevelEvent->eventsDefault();
|
||||
} else if (isStateEvent) {
|
||||
return powerLevelEvent->powerLevelForState(eventName);
|
||||
@@ -1164,7 +1163,7 @@ int NeoChatRoom::powerLevel(const QString &eventName, const bool &isStateEvent)
|
||||
|
||||
void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLevel, const bool &isStateEvent)
|
||||
{
|
||||
auto powerLevelContent = currentState().get("m.room.power_levels"_ls)->contentJson();
|
||||
auto powerLevelContent = currentState().get("m.room.power_levels")->contentJson();
|
||||
int clampPowerLevel = std::clamp(newPowerLevel, 0, 100);
|
||||
int powerLevel = 0;
|
||||
|
||||
@@ -1175,215 +1174,215 @@ void NeoChatRoom::setPowerLevel(const QString &eventName, const int &newPowerLev
|
||||
powerLevelContent[eventName] = clampPowerLevel;
|
||||
}
|
||||
} else {
|
||||
auto eventPowerLevels = powerLevelContent["events"_ls].toObject();
|
||||
auto eventPowerLevels = powerLevelContent["events"].toObject();
|
||||
|
||||
if (eventPowerLevels.contains(eventName)) {
|
||||
powerLevel = eventPowerLevels[eventName].toInt();
|
||||
} else {
|
||||
if (isStateEvent) {
|
||||
powerLevel = powerLevelContent["state_default"_ls].toInt();
|
||||
powerLevel = powerLevelContent["state_default"].toInt();
|
||||
} else {
|
||||
powerLevel = powerLevelContent["events_default"_ls].toInt();
|
||||
powerLevel = powerLevelContent["events_default"].toInt();
|
||||
}
|
||||
}
|
||||
|
||||
if (powerLevel != clampPowerLevel) {
|
||||
eventPowerLevels[eventName] = clampPowerLevel;
|
||||
powerLevelContent["events"_ls] = eventPowerLevels;
|
||||
powerLevelContent["events"] = eventPowerLevels;
|
||||
}
|
||||
}
|
||||
|
||||
setState("m.room.power_levels"_ls, {}, powerLevelContent);
|
||||
setState("m.room.power_levels", "", powerLevelContent);
|
||||
}
|
||||
|
||||
int NeoChatRoom::defaultUserPowerLevel() const
|
||||
{
|
||||
return powerLevel("users_default"_ls);
|
||||
return powerLevel("users_default");
|
||||
}
|
||||
|
||||
void NeoChatRoom::setDefaultUserPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("users_default"_ls, newPowerLevel);
|
||||
setPowerLevel("users_default", newPowerLevel);
|
||||
}
|
||||
|
||||
int NeoChatRoom::invitePowerLevel() const
|
||||
{
|
||||
return powerLevel("invite"_ls);
|
||||
return powerLevel("invite");
|
||||
}
|
||||
|
||||
void NeoChatRoom::setInvitePowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("invite"_ls, newPowerLevel);
|
||||
setPowerLevel("invite", newPowerLevel);
|
||||
}
|
||||
|
||||
int NeoChatRoom::kickPowerLevel() const
|
||||
{
|
||||
return powerLevel("kick"_ls);
|
||||
return powerLevel("kick");
|
||||
}
|
||||
|
||||
void NeoChatRoom::setKickPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("kick"_ls, newPowerLevel);
|
||||
setPowerLevel("kick", newPowerLevel);
|
||||
}
|
||||
|
||||
int NeoChatRoom::banPowerLevel() const
|
||||
{
|
||||
return powerLevel("ban"_ls);
|
||||
return powerLevel("ban");
|
||||
}
|
||||
|
||||
void NeoChatRoom::setBanPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("ban"_ls, newPowerLevel);
|
||||
setPowerLevel("ban", newPowerLevel);
|
||||
}
|
||||
|
||||
int NeoChatRoom::redactPowerLevel() const
|
||||
{
|
||||
return powerLevel("redact"_ls);
|
||||
return powerLevel("redact");
|
||||
}
|
||||
|
||||
void NeoChatRoom::setRedactPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("redact"_ls, newPowerLevel);
|
||||
setPowerLevel("redact", newPowerLevel);
|
||||
}
|
||||
|
||||
int NeoChatRoom::statePowerLevel() const
|
||||
{
|
||||
return powerLevel("state_default"_ls);
|
||||
return powerLevel("state_default");
|
||||
}
|
||||
|
||||
void NeoChatRoom::setStatePowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("state_default"_ls, newPowerLevel);
|
||||
setPowerLevel("state_default", newPowerLevel);
|
||||
}
|
||||
|
||||
int NeoChatRoom::defaultEventPowerLevel() const
|
||||
{
|
||||
return powerLevel("events_default"_ls);
|
||||
return powerLevel("events_default");
|
||||
}
|
||||
|
||||
void NeoChatRoom::setDefaultEventPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("events_default"_ls, newPowerLevel);
|
||||
setPowerLevel("events_default", newPowerLevel);
|
||||
}
|
||||
|
||||
int NeoChatRoom::powerLevelPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.power_levels"_ls, true);
|
||||
return powerLevel("m.room.power_levels", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setPowerLevelPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.power_levels"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.power_levels", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::namePowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.name"_ls, true);
|
||||
return powerLevel("m.room.name", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setNamePowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.name"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.name", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::avatarPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.avatar"_ls, true);
|
||||
return powerLevel("m.room.avatar", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setAvatarPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.avatar"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.avatar", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::canonicalAliasPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.canonical_alias"_ls, true);
|
||||
return powerLevel("m.room.canonical_alias", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setCanonicalAliasPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.canonical_alias"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.canonical_alias", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::topicPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.topic"_ls, true);
|
||||
return powerLevel("m.room.topic", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setTopicPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.topic"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.topic", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::encryptionPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.encryption"_ls, true);
|
||||
return powerLevel("m.room.encryption", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setEncryptionPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.encryption"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.encryption", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::historyVisibilityPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.history_visibility"_ls, true);
|
||||
return powerLevel("m.room.history_visibility", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setHistoryVisibilityPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.history_visibility"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.history_visibility", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::pinnedEventsPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.pinned_events"_ls, true);
|
||||
return powerLevel("m.room.pinned_events", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setPinnedEventsPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.pinned_events"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.pinned_events", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::tombstonePowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.tombstone"_ls, true);
|
||||
return powerLevel("m.room.tombstone", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setTombstonePowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.tombstone"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.tombstone", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::serverAclPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.room.server_acl"_ls, true);
|
||||
return powerLevel("m.room.server_acl", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setServerAclPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.room.server_acl"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.room.server_acl", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::spaceChildPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.space.child"_ls, true);
|
||||
return powerLevel("m.space.child", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setSpaceChildPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.space.child"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.space.child", newPowerLevel, true);
|
||||
}
|
||||
|
||||
int NeoChatRoom::spaceParentPowerLevel() const
|
||||
{
|
||||
return powerLevel("m.space.parent"_ls, true);
|
||||
return powerLevel("m.space.parent", true);
|
||||
}
|
||||
|
||||
void NeoChatRoom::setSpaceParentPowerLevel(const int &newPowerLevel)
|
||||
{
|
||||
setPowerLevel("m.space.parent"_ls, newPowerLevel, true);
|
||||
setPowerLevel("m.space.parent", newPowerLevel, true);
|
||||
}
|
||||
|
||||
QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QString reason)
|
||||
@@ -1395,7 +1394,7 @@ QCoro::Task<void> NeoChatRoom::doDeleteMessagesByUser(const QString &user, QStri
|
||||
}
|
||||
}
|
||||
for (const auto &e : events) {
|
||||
auto job = connection()->callApi<RedactEventJob>(id(), QString::fromLatin1(QUrl::toPercentEncoding(e)), connection()->generateTxnId(), reason);
|
||||
auto job = connection()->callApi<RedactEventJob>(id(), QUrl::toPercentEncoding(e), connection()->generateTxnId(), reason);
|
||||
co_await qCoro(job, &BaseJob::finished);
|
||||
if (job->error() != BaseJob::Success) {
|
||||
qWarning() << "Error: \"" << job->error() << "\" while deleting messages. Aborting";
|
||||
@@ -1446,26 +1445,26 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
* Note to prevent race conditions any rule that is going ot be overridden later is not removed.
|
||||
* If the default push notification state is chosen any exisiting rule needs to be removed.
|
||||
*/
|
||||
QJsonObject accountData = connection()->accountDataJson("m.push_rules"_ls);
|
||||
QJsonObject accountData = connection()->accountDataJson("m.push_rules");
|
||||
|
||||
// For default and mute check for a room rule and remove if found.
|
||||
if (state == PushNotificationState::Default || state == PushNotificationState::Mute) {
|
||||
QJsonArray roomRuleArray = accountData["global"_ls].toObject()["room"_ls].toArray();
|
||||
QJsonArray roomRuleArray = accountData["global"].toObject()["room"].toArray();
|
||||
for (const auto &i : roomRuleArray) {
|
||||
QJsonObject roomRule = i.toObject();
|
||||
if (roomRule["rule_id"_ls] == id()) {
|
||||
Controller::instance().activeConnection()->callApi<DeletePushRuleJob>("global"_ls, "room"_ls, id());
|
||||
if (roomRule["rule_id"] == id()) {
|
||||
Controller::instance().activeConnection()->callApi<DeletePushRuleJob>("global", "room", id());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// For default, all and @mentions and keywords check for an override rule and remove if found.
|
||||
if (state == PushNotificationState::Default || state == PushNotificationState::All || state == PushNotificationState::MentionKeyword) {
|
||||
QJsonArray overrideRuleArray = accountData["global"_ls].toObject()["override"_ls].toArray();
|
||||
QJsonArray overrideRuleArray = accountData["global"].toObject()["override"].toArray();
|
||||
for (const auto &i : overrideRuleArray) {
|
||||
QJsonObject overrideRule = i.toObject();
|
||||
if (overrideRule["rule_id"_ls] == id()) {
|
||||
Controller::instance().activeConnection()->callApi<DeletePushRuleJob>("global"_ls, "override"_ls, id());
|
||||
if (overrideRule["rule_id"] == id()) {
|
||||
Controller::instance().activeConnection()->callApi<DeletePushRuleJob>("global", "override", id());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1481,7 +1480,7 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
* "don't_notify"
|
||||
* ]
|
||||
*/
|
||||
const QVector<QVariant> actions = {"dont_notify"_ls};
|
||||
const QVector<QVariant> actions = {"dont_notify"};
|
||||
/**
|
||||
* Setup the push condition to get all events for the current room
|
||||
* see https://spec.matrix.org/v1.3/client-server-api/#conditions-1
|
||||
@@ -1495,17 +1494,15 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
* ]
|
||||
*/
|
||||
PushCondition pushCondition;
|
||||
pushCondition.kind = "event_match"_ls;
|
||||
pushCondition.key = "room_id"_ls;
|
||||
pushCondition.kind = "event_match";
|
||||
pushCondition.key = "room_id";
|
||||
pushCondition.pattern = id();
|
||||
const QVector<PushCondition> conditions = {pushCondition};
|
||||
|
||||
// Add new override rule and make sure it's enabled
|
||||
auto job = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<SetPushRuleJob>("global"_ls, "override"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
auto job = Controller::instance().activeConnection()->callApi<SetPushRuleJob>("global", "override", id(), actions, "", "", conditions, "");
|
||||
connect(job, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global"_ls, "override"_ls, id(), true);
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global", "override", id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1525,15 +1522,13 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
* "don't_notify"
|
||||
* ]
|
||||
*/
|
||||
const QVector<QVariant> actions = {"dont_notify"_ls};
|
||||
const QVector<QVariant> actions = {"dont_notify"};
|
||||
// No conditions for a room rule
|
||||
const QVector<PushCondition> conditions;
|
||||
|
||||
auto setJob = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
auto setJob = Controller::instance().activeConnection()->callApi<SetPushRuleJob>("global", "room", id(), actions, "", "", conditions, "");
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global", "room", id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1555,18 +1550,16 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
* ]
|
||||
*/
|
||||
QJsonObject tweaks;
|
||||
tweaks.insert("set_tweak"_ls, "sound"_ls);
|
||||
tweaks.insert("value"_ls, "default"_ls);
|
||||
const QVector<QVariant> actions = {"notify"_ls, tweaks};
|
||||
tweaks.insert("set_tweak", "sound");
|
||||
tweaks.insert("value", "default");
|
||||
const QVector<QVariant> actions = {"notify", tweaks};
|
||||
// No conditions for a room rule
|
||||
const QVector<PushCondition> conditions;
|
||||
|
||||
// Add new room rule and make sure enabled
|
||||
auto setJob = Controller::instance()
|
||||
.activeConnection()
|
||||
->callApi<SetPushRuleJob>("global"_ls, "room"_ls, id(), actions, QString(), QString(), conditions, QString());
|
||||
auto setJob = Controller::instance().activeConnection()->callApi<SetPushRuleJob>("global", "room", id(), actions, "", "", conditions, "");
|
||||
connect(setJob, &BaseJob::success, this, [this]() {
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global"_ls, "room"_ls, id(), true);
|
||||
auto enableJob = Controller::instance().activeConnection()->callApi<SetPushRuleEnabledJob>("global", "room", id(), true);
|
||||
connect(enableJob, &BaseJob::success, this, [this]() {
|
||||
m_pushNotificationStateUpdating = false;
|
||||
});
|
||||
@@ -1580,23 +1573,23 @@ void NeoChatRoom::setPushNotificationState(PushNotificationState::State state)
|
||||
|
||||
void NeoChatRoom::updatePushNotificationState(QString type)
|
||||
{
|
||||
if (type != "m.push_rules"_ls || m_pushNotificationStateUpdating) {
|
||||
if (type != "m.push_rules" || m_pushNotificationStateUpdating) {
|
||||
return;
|
||||
}
|
||||
|
||||
QJsonObject accountData = connection()->accountDataJson("m.push_rules"_ls);
|
||||
QJsonObject accountData = connection()->accountDataJson("m.push_rules");
|
||||
|
||||
// First look for a room rule with the room id
|
||||
QJsonArray roomRuleArray = accountData["global"_ls].toObject()["room"_ls].toArray();
|
||||
QJsonArray roomRuleArray = accountData["global"].toObject()["room"].toArray();
|
||||
for (const auto &i : roomRuleArray) {
|
||||
QJsonObject roomRule = i.toObject();
|
||||
if (roomRule["rule_id"_ls] == id()) {
|
||||
QString notifyAction = roomRule["actions"_ls].toArray()[0].toString();
|
||||
if (notifyAction == "notify"_ls) {
|
||||
if (roomRule["rule_id"] == id()) {
|
||||
QString notifyAction = roomRule["actions"].toArray()[0].toString();
|
||||
if (notifyAction == "notify") {
|
||||
m_currentPushNotificationState = PushNotificationState::All;
|
||||
Q_EMIT pushNotificationStateChanged(m_currentPushNotificationState);
|
||||
return;
|
||||
} else if (notifyAction == "dont_notify"_ls) {
|
||||
} else if (notifyAction == "dont_notify") {
|
||||
m_currentPushNotificationState = PushNotificationState::MentionKeyword;
|
||||
Q_EMIT pushNotificationStateChanged(m_currentPushNotificationState);
|
||||
return;
|
||||
@@ -1605,12 +1598,12 @@ void NeoChatRoom::updatePushNotificationState(QString type)
|
||||
}
|
||||
|
||||
// Check for an override rule with the room id
|
||||
QJsonArray overrideRuleArray = accountData["global"_ls].toObject()["override"_ls].toArray();
|
||||
QJsonArray overrideRuleArray = accountData["global"].toObject()["override"].toArray();
|
||||
for (const auto &i : overrideRuleArray) {
|
||||
QJsonObject overrideRule = i.toObject();
|
||||
if (overrideRule["rule_id"_ls] == id()) {
|
||||
QString notifyAction = overrideRule["actions"_ls].toArray()[0].toString();
|
||||
if (notifyAction == "dont_notify"_ls) {
|
||||
if (overrideRule["rule_id"] == id()) {
|
||||
QString notifyAction = overrideRule["actions"].toArray()[0].toString();
|
||||
if (notifyAction == "dont_notify") {
|
||||
m_currentPushNotificationState = PushNotificationState::Mute;
|
||||
Q_EMIT pushNotificationStateChanged(m_currentPushNotificationState);
|
||||
return;
|
||||
@@ -1689,7 +1682,7 @@ QVariantMap NeoChatRoom::chatBoxReplyUser() const
|
||||
if (m_chatBoxReplyId.isEmpty()) {
|
||||
return emptyUser;
|
||||
}
|
||||
return getUser(user((*findInTimeline(m_chatBoxReplyId))->senderId()));
|
||||
return getUser(static_cast<NeoChatUser *>(user((*findInTimeline(m_chatBoxReplyId))->senderId())));
|
||||
}
|
||||
|
||||
QString NeoChatRoom::chatBoxReplyMessage() const
|
||||
@@ -1705,7 +1698,7 @@ QVariantMap NeoChatRoom::chatBoxEditUser() const
|
||||
if (m_chatBoxEditId.isEmpty()) {
|
||||
return emptyUser;
|
||||
}
|
||||
return getUser(user((*findInTimeline(m_chatBoxEditId))->senderId()));
|
||||
return getUser(static_cast<NeoChatUser *>(user((*findInTimeline(m_chatBoxEditId))->senderId())));
|
||||
}
|
||||
|
||||
QString NeoChatRoom::chatBoxEditMessage() const
|
||||
@@ -1766,9 +1759,9 @@ void NeoChatRoom::replyLastMessage()
|
||||
|
||||
if (e->msgtype() != MessageEventType::Unknown) {
|
||||
QString eventId;
|
||||
if (content.contains("m.new_content"_ls)) {
|
||||
if (content.contains("m.new_content")) {
|
||||
// The message has been edited so we have to return the id of the original message instead of the replacement
|
||||
eventId = content["m.relates_to"_ls].toObject()["event_id"_ls].toString();
|
||||
eventId = content["m.relates_to"].toObject()["event_id"].toString();
|
||||
} else {
|
||||
// For any message that isn't an edit return the id of the current message
|
||||
eventId = (*it)->id();
|
||||
@@ -1800,9 +1793,9 @@ void NeoChatRoom::editLastMessage()
|
||||
|
||||
if (e->msgtype() != MessageEventType::Unknown) {
|
||||
QString eventId;
|
||||
if (content.contains("m.new_content"_ls)) {
|
||||
if (content.contains("m.new_content")) {
|
||||
// The message has been edited so we have to return the id of the original message instead of the replacement
|
||||
eventId = content["m.relates_to"_ls].toObject()["event_id"_ls].toString();
|
||||
eventId = content["m.relates_to"].toObject()["event_id"].toString();
|
||||
} else {
|
||||
// For any message that isn't an edit return the id of the current message
|
||||
eventId = (*it)->id();
|
||||
@@ -1816,7 +1809,10 @@ void NeoChatRoom::editLastMessage()
|
||||
|
||||
bool NeoChatRoom::canEncryptRoom() const
|
||||
{
|
||||
return !usesEncryption() && canSendState("m.room.encryption"_ls);
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
return !usesEncryption() && canSendState("m.room.encryption");
|
||||
#endif
|
||||
return false;
|
||||
}
|
||||
|
||||
PollHandler *NeoChatRoom::poll(const QString &eventId)
|
||||
@@ -1908,34 +1904,33 @@ int NeoChatRoom::maxRoomVersion() const
|
||||
}
|
||||
return maxVersion;
|
||||
}
|
||||
|
||||
Quotient::User *NeoChatRoom::directChatRemoteUser() const
|
||||
NeoChatUser *NeoChatRoom::directChatRemoteUser() const
|
||||
{
|
||||
return connection()->directChatUsers(this)[0];
|
||||
return dynamic_cast<NeoChatUser *>(connection()->directChatUsers(this)[0]);
|
||||
}
|
||||
|
||||
void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)
|
||||
{
|
||||
QJsonObject locationContent{
|
||||
{"uri"_ls, "geo:%1,%2"_ls.arg(QString::number(lat), QString::number(lon))},
|
||||
{"uri", "geo:%1,%2"_ls.arg(QString::number(lat), QString::number(lon))},
|
||||
};
|
||||
|
||||
if (!description.isEmpty()) {
|
||||
locationContent["description"_ls] = description;
|
||||
locationContent["description"] = description;
|
||||
}
|
||||
|
||||
QJsonObject content{
|
||||
{"body"_ls, i18nc("'Lat' and 'Lon' as in Latitude and Longitude", "Lat: %1, Lon: %2", lat, lon)},
|
||||
{"msgtype"_ls, "m.location"_ls},
|
||||
{"geo_uri"_ls, "geo:%1,%2"_ls.arg(QString::number(lat), QString::number(lon))},
|
||||
{"org.matrix.msc3488.location"_ls, locationContent},
|
||||
{"org.matrix.msc3488.asset"_ls,
|
||||
{"body", i18nc("'Lat' and 'Lon' as in Latitude and Longitude", "Lat: %1, Lon: %2", lat, lon)},
|
||||
{"msgtype", "m.location"},
|
||||
{"geo_uri", "geo:%1,%2"_ls.arg(QString::number(lat), QString::number(lon))},
|
||||
{"org.matrix.msc3488.location", locationContent},
|
||||
{"org.matrix.msc3488.asset",
|
||||
QJsonObject{
|
||||
{"type"_ls, "m.pin"_ls},
|
||||
{"type", "m.pin"},
|
||||
}},
|
||||
{"org.matrix.msc1767.text"_ls, i18nc("'Lat' and 'Lon' as in Latitude and Longitude", "Lat: %1, Lon: %2", lat, lon)},
|
||||
{"org.matrix.msc1767.text", i18nc("'Lat' and 'Lon' as in Latitude and Longitude", "Lat: %1, Lon: %2", lat, lon)},
|
||||
};
|
||||
postJson("m.room.message"_ls, content);
|
||||
postJson("m.room.message", content);
|
||||
}
|
||||
|
||||
QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType)
|
||||
@@ -1943,7 +1938,7 @@ QByteArray NeoChatRoom::roomAcountDataJson(const QString &eventType)
|
||||
return QJsonDocument(accountData(eventType)->fullJson()).toJson();
|
||||
}
|
||||
|
||||
QUrl NeoChatRoom::avatarForMember(Quotient::User *user) const
|
||||
QUrl NeoChatRoom::avatarForMember(NeoChatUser *user) const
|
||||
{
|
||||
const auto &url = memberAvatarUrl(user->id());
|
||||
if (url.isEmpty() || url.scheme() != "mxc"_ls) {
|
||||
@@ -1959,7 +1954,7 @@ QUrl NeoChatRoom::avatarForMember(Quotient::User *user) const
|
||||
|
||||
const RoomEvent *NeoChatRoom::getReplyForEvent(const RoomEvent &event) const
|
||||
{
|
||||
const QString &replyEventId = event.contentJson()["m.relates_to"_ls].toObject()["m.in_reply_to"_ls].toObject()["event_id"_ls].toString();
|
||||
const QString &replyEventId = event.contentJson()["m.relates_to"].toObject()["m.in_reply_to"].toObject()["event_id"].toString();
|
||||
if (replyEventId.isEmpty()) {
|
||||
return {};
|
||||
};
|
||||
|
||||
@@ -10,15 +10,10 @@
|
||||
#include <QTextCursor>
|
||||
|
||||
#include <QCoroTask>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
#include "neochatuser.h"
|
||||
#include "pollhandler.h"
|
||||
|
||||
namespace Quotient
|
||||
{
|
||||
class User;
|
||||
}
|
||||
|
||||
class PushNotificationState : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
@@ -69,7 +64,7 @@ class NeoChatRoom : public Quotient::Room
|
||||
*
|
||||
* The list does not include the local user.
|
||||
*
|
||||
* This is different to getting a list of Quotient::User objects
|
||||
* This is different to getting a list of NeoChatUser objects or Quotient::User objects
|
||||
* as neither of those can provide details like the displayName or avatarMediaId
|
||||
* without the room context as these can vary from room to room. This function
|
||||
* provides the room context and puts the result as a list of QVariantMap objects.
|
||||
@@ -81,7 +76,7 @@ class NeoChatRoom : public Quotient::Room
|
||||
* - displayName - Display name in the context of this room.
|
||||
* - display - Name in the context of this room.
|
||||
*
|
||||
* @sa Quotient::User
|
||||
* @sa Quotient::User, NeoChatUser
|
||||
*/
|
||||
Q_PROPERTY(QVariantList usersTyping READ getUsersTyping NOTIFY typingChanged)
|
||||
|
||||
@@ -124,7 +119,7 @@ class NeoChatRoom : public Quotient::Room
|
||||
/**
|
||||
* @brief Get a user object for the other person in a direct chat.
|
||||
*/
|
||||
Q_PROPERTY(Quotient::User *directChatRemoteUser READ directChatRemoteUser CONSTANT)
|
||||
Q_PROPERTY(NeoChatUser *directChatRemoteUser READ directChatRemoteUser CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief If the room is a space.
|
||||
@@ -186,6 +181,9 @@ class NeoChatRoom : public Quotient::Room
|
||||
/**
|
||||
* @brief Whether the local user can encrypt the room.
|
||||
*
|
||||
* Requires libQuotient 0.7 compiled with the Quotient_E2EE_ENABLED parameter to
|
||||
* be able to return true.
|
||||
*
|
||||
* A local user can encrypt a room if they have permission to send the m.room.encryption
|
||||
* state event.
|
||||
*
|
||||
@@ -318,7 +316,7 @@ class NeoChatRoom : public Quotient::Room
|
||||
/**
|
||||
* @brief Get the user for the message being replied to.
|
||||
*
|
||||
* This is different to getting a Quotient::User object
|
||||
* This is different to getting a NeoChatUser object or Quotient::User object
|
||||
* as neither of those can provide details like the displayName or avatarMediaId
|
||||
* without the room context as these can vary from room to room.
|
||||
*
|
||||
@@ -331,9 +329,9 @@ class NeoChatRoom : public Quotient::Room
|
||||
* - avatarSource - The mxc URL for the user's avatar in the current room.
|
||||
* - avatarMediaId - Avatar id in the context of this room.
|
||||
* - color - Color for the user.
|
||||
* - object - The Quotient::User object for the user.
|
||||
* - object - The NeoChatUser object for the user.
|
||||
*
|
||||
* @sa getUser, Quotient::User
|
||||
* @sa getUser, Quotient::User, NeoChatUser
|
||||
*/
|
||||
Q_PROPERTY(QVariantMap chatBoxReplyUser READ chatBoxReplyUser NOTIFY chatBoxReplyIdChanged)
|
||||
|
||||
@@ -347,7 +345,7 @@ class NeoChatRoom : public Quotient::Room
|
||||
/**
|
||||
* @brief Get the user for the message being edited.
|
||||
*
|
||||
* This is different to getting a Quotient::User object
|
||||
* This is different to getting a NeoChatUser object or Quotient::User object
|
||||
* as neither of those can provide details like the displayName or avatarMediaId
|
||||
* without the room context as these can vary from room to room.
|
||||
*
|
||||
@@ -360,9 +358,9 @@ class NeoChatRoom : public Quotient::Room
|
||||
* - avatarSource - The mxc URL for the user's avatar in the current room.
|
||||
* - avatarMediaId - Avatar id in the context of this room.
|
||||
* - color - Color for the user.
|
||||
* - object - The Quotient::User object for the user.
|
||||
* - object - The NeoChatUser object for the user.
|
||||
*
|
||||
* @sa getUser, Quotient::User
|
||||
* @sa getUser, Quotient::User, NeoChatUser
|
||||
*/
|
||||
Q_PROPERTY(QVariantMap chatBoxEditUser READ chatBoxEditUser NOTIFY chatBoxEditIdChanged)
|
||||
|
||||
@@ -394,7 +392,7 @@ public:
|
||||
/**
|
||||
* @brief Get a list of users in the context of this room.
|
||||
*
|
||||
* This is different to getting a list of Quotient::User objects
|
||||
* This is different to getting a list of NeoChatUser objects or Quotient::User objects
|
||||
* as neither of those can provide details like the displayName or avatarMediaId
|
||||
* without the room context as these can vary from room to room. This function
|
||||
* provides the room context and returns the result as a list of QVariantMap objects.
|
||||
@@ -409,14 +407,14 @@ public:
|
||||
* - avatarMediaId - Avatar id in the context of this room.
|
||||
* - color - Color for the user.
|
||||
*
|
||||
* @sa Quotient::User
|
||||
* @sa Quotient::User, NeoChatUser
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] QVariantList getUsers(const QString &keyword, int limit = -1) const;
|
||||
|
||||
/**
|
||||
* @brief Get a user in the context of this room.
|
||||
*
|
||||
* This is different to getting a Quotient::User object
|
||||
* This is different to getting a NeoChatUser object or Quotient::User object
|
||||
* as neither of those can provide details like the displayName or avatarMediaId
|
||||
* without the room context as these can vary from room to room. This function
|
||||
* provides the room context and outputs the result as QVariantMap.
|
||||
@@ -433,16 +431,16 @@ public:
|
||||
* - avatarSource - The mxc URL for the user's avatar in the current room.
|
||||
* - avatarMediaId - Avatar id in the context of this room.
|
||||
* - color - Color for the user.
|
||||
* - object - The Quotient::User object for the user.
|
||||
* - object - The NeoChatUser object for the user.
|
||||
*
|
||||
* @sa Quotient::User
|
||||
* @sa Quotient::User, NeoChatUser
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(const QString &userID) const;
|
||||
|
||||
/**
|
||||
* @brief Get a user in the context of this room.
|
||||
*
|
||||
* This is different to getting a Quotient::User object
|
||||
* This is different to getting a NeoChatUser object or Quotient::User object
|
||||
* as neither of those can provide details like the displayName or avatarMediaId
|
||||
* without the room context as these can vary from room to room. This function
|
||||
* provides the room context and outputs the result as QVariantMap.
|
||||
@@ -459,11 +457,11 @@ public:
|
||||
* - avatarSource - The mxc URL for the user's avatar in the current room.
|
||||
* - avatarMediaId - Avatar id in the context of this room.
|
||||
* - color - Color for the user.
|
||||
* - object - The Quotient::User object for the user.
|
||||
* - object - The NeoChatUser object for the user.
|
||||
*
|
||||
* @sa Quotient::User
|
||||
* @sa Quotient::User, NeoChatUser
|
||||
*/
|
||||
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(Quotient::User *user) const;
|
||||
Q_INVOKABLE [[nodiscard]] QVariantMap getUser(NeoChatUser *user) const;
|
||||
|
||||
[[nodiscard]] QVariantList getUsersTyping() const;
|
||||
|
||||
@@ -604,7 +602,7 @@ public:
|
||||
|
||||
[[nodiscard]] QString avatarMediaId() const;
|
||||
|
||||
Quotient::User *directChatRemoteUser() const;
|
||||
NeoChatUser *directChatRemoteUser() const;
|
||||
|
||||
[[nodiscard]] bool isSpace();
|
||||
|
||||
@@ -820,7 +818,7 @@ public:
|
||||
*/
|
||||
Q_INVOKABLE QByteArray roomAcountDataJson(const QString &eventType);
|
||||
|
||||
Q_INVOKABLE [[nodiscard]] QUrl avatarForMember(Quotient::User *user) const;
|
||||
Q_INVOKABLE [[nodiscard]] QUrl avatarForMember(NeoChatUser *user) const;
|
||||
|
||||
/**
|
||||
* @brief Returns the event that is being replied to. This includes events that were manually loaded using NeoChatRoom::loadReply.
|
||||
|
||||
43
src/neochatuser.cpp
Normal file
43
src/neochatuser.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#include "neochatuser.h"
|
||||
|
||||
#include <QGuiApplication>
|
||||
#include <QJsonObject>
|
||||
#include <QPalette>
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
NeoChatUser::NeoChatUser(QString userId, Connection *connection)
|
||||
: User(std::move(userId), connection)
|
||||
{
|
||||
connect(static_cast<QGuiApplication *>(QGuiApplication::instance()), &QGuiApplication::paletteChanged, this, &NeoChatUser::polishColor);
|
||||
polishColor();
|
||||
}
|
||||
|
||||
QColor NeoChatUser::color()
|
||||
{
|
||||
return m_color;
|
||||
}
|
||||
|
||||
void NeoChatUser::setColor(const QColor &color)
|
||||
{
|
||||
if (m_color == color) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_color = color;
|
||||
Q_EMIT colorChanged(m_color);
|
||||
}
|
||||
|
||||
void NeoChatUser::polishColor()
|
||||
{
|
||||
const auto lightness = static_cast<QGuiApplication *>(QGuiApplication::instance())->palette().color(QPalette::Active, QPalette::Window).lightnessF();
|
||||
// https://github.com/quotient-im/libQuotient/wiki/User-color-coding-standard-draft-proposal
|
||||
setColor(QColor::fromHslF(hueF(), 1, -0.7 * lightness + 0.9, 1));
|
||||
}
|
||||
|
||||
#include "moc_neochatuser.cpp"
|
||||
39
src/neochatuser.h
Normal file
39
src/neochatuser.h
Normal file
@@ -0,0 +1,39 @@
|
||||
// SPDX-FileCopyrightText: 2018-2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
|
||||
#include <Quotient/user.h>
|
||||
|
||||
/**
|
||||
* @class NeoChatUser
|
||||
*
|
||||
* A class inherited from Quotient::User to add a user color function.
|
||||
*
|
||||
* @sa Quotient::User
|
||||
*/
|
||||
class NeoChatUser : public Quotient::User
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
/**
|
||||
* @brief The color to use for the user.
|
||||
*/
|
||||
Q_PROPERTY(QColor color READ color WRITE setColor NOTIFY colorChanged)
|
||||
public:
|
||||
explicit NeoChatUser(QString userId, Quotient::Connection *connection);
|
||||
|
||||
public Q_SLOTS:
|
||||
QColor color();
|
||||
void setColor(const QColor &color);
|
||||
|
||||
Q_SIGNALS:
|
||||
void colorChanged(QColor _t1);
|
||||
|
||||
private:
|
||||
QColor m_color;
|
||||
|
||||
void polishColor();
|
||||
};
|
||||
@@ -62,7 +62,7 @@ void NotificationsManager::processNotificationJob(QPointer<Quotient::Connection>
|
||||
const auto connectionId = connection->user()->id();
|
||||
|
||||
// If pagination has occurred set off the next job
|
||||
auto nextToken = job->jsonData()["next_token"_ls].toString();
|
||||
auto nextToken = job->jsonData()["next_token"].toString();
|
||||
if (!nextToken.isEmpty()) {
|
||||
auto nextJob = connection->callApi<GetNotificationsJob>(nextToken);
|
||||
m_connActiveJob.append(connectionId);
|
||||
@@ -72,58 +72,60 @@ void NotificationsManager::processNotificationJob(QPointer<Quotient::Connection>
|
||||
});
|
||||
}
|
||||
|
||||
const auto notifications = job->jsonData()["notifications"_ls].toArray();
|
||||
const auto notifications = job->jsonData()["notifications"].toArray();
|
||||
if (initialization) {
|
||||
m_oldNotifications[connectionId] = QStringList();
|
||||
for (const auto &n : notifications) {
|
||||
if (!m_initialTimestamp.contains(connectionId)) {
|
||||
m_initialTimestamp[connectionId] = n.toObject()["ts"_ls].toDouble();
|
||||
m_initialTimestamp[connectionId] = n.toObject()["ts"].toDouble();
|
||||
} else {
|
||||
qint64 timestamp = n.toObject()["ts"_ls].toDouble();
|
||||
qint64 timestamp = n.toObject()["ts"].toDouble();
|
||||
if (timestamp > m_initialTimestamp[connectionId]) {
|
||||
m_initialTimestamp[connectionId] = timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
auto connectionNotifications = m_oldNotifications.value(connectionId);
|
||||
connectionNotifications += n.toObject()["event"_ls].toObject()["event_id"_ls].toString();
|
||||
connectionNotifications += n.toObject()["event"].toObject()["event_id"].toString();
|
||||
m_oldNotifications[connectionId] = connectionNotifications;
|
||||
}
|
||||
return;
|
||||
}
|
||||
for (const auto &n : notifications) {
|
||||
const auto notification = n.toObject();
|
||||
if (notification["read"_ls].toBool()) {
|
||||
if (notification["read"].toBool()) {
|
||||
continue;
|
||||
}
|
||||
auto connectionNotifications = m_oldNotifications.value(connectionId);
|
||||
if (connectionNotifications.contains(notification["event"_ls].toObject()["event_id"_ls].toString())) {
|
||||
if (connectionNotifications.contains(notification["event"].toObject()["event_id"].toString())) {
|
||||
continue;
|
||||
}
|
||||
connectionNotifications += notification["event"_ls].toObject()["event_id"_ls].toString();
|
||||
connectionNotifications += notification["event"].toObject()["event_id"].toString();
|
||||
m_oldNotifications[connectionId] = connectionNotifications;
|
||||
|
||||
auto room = connection->room(notification["room_id"_ls].toString());
|
||||
auto room = connection->room(notification["room_id"].toString());
|
||||
if (shouldPostNotification(connection, n)) {
|
||||
// The room might have been deleted (for example rejected invitation).
|
||||
auto sender = room->user(notification["event"_ls].toObject()["sender"_ls].toString());
|
||||
auto sender = room->user(notification["event"].toObject()["sender"].toString());
|
||||
|
||||
QString body;
|
||||
|
||||
if (notification["event"_ls].toObject()["type"_ls].toString() == "org.matrix.msc3381.poll.start"_ls) {
|
||||
body = notification["event"_ls]
|
||||
.toObject()["content"_ls]
|
||||
.toObject()["org.matrix.msc3381.poll.start"_ls]
|
||||
.toObject()["question"_ls]
|
||||
.toObject()["body"_ls]
|
||||
if (notification["event"].toObject()["type"].toString() == "org.matrix.msc3381.poll.start") {
|
||||
body = notification["event"]
|
||||
.toObject()["content"]
|
||||
.toObject()["org.matrix.msc3381.poll.start"]
|
||||
.toObject()["question"]
|
||||
.toObject()["body"]
|
||||
.toString();
|
||||
} else {
|
||||
body = notification["event"_ls].toObject()["content"_ls].toObject()["body"_ls].toString();
|
||||
body = notification["event"].toObject()["content"].toObject()["body"].toString();
|
||||
}
|
||||
|
||||
if (notification["event"_ls]["type"_ls] == "m.room.encrypted"_ls) {
|
||||
if (notification["event"]["type"] == "m.room.encrypted") {
|
||||
#ifdef Quotient_E2EE_ENABLED
|
||||
auto decrypted = connection->decryptNotification(notification);
|
||||
body = decrypted["content"_ls].toObject()["body"_ls].toString();
|
||||
body = decrypted["content"].toObject()["body"].toString();
|
||||
#endif
|
||||
if (body.isEmpty()) {
|
||||
body = i18n("Encrypted Message");
|
||||
}
|
||||
@@ -139,7 +141,7 @@ void NotificationsManager::processNotificationJob(QPointer<Quotient::Connection>
|
||||
sender->displayname(room),
|
||||
body,
|
||||
avatar_image,
|
||||
notification["event"_ls].toObject()["event_id"_ls].toString(),
|
||||
notification["event"].toObject()["event_id"].toString(),
|
||||
true);
|
||||
}
|
||||
}
|
||||
@@ -151,7 +153,7 @@ bool NotificationsManager::shouldPostNotification(QPointer<Quotient::Connection>
|
||||
return false;
|
||||
}
|
||||
|
||||
auto room = connection->room(notification["room_id"_ls].toString());
|
||||
auto room = connection->room(notification["room_id"].toString());
|
||||
if (room == nullptr) {
|
||||
return false;
|
||||
}
|
||||
@@ -167,7 +169,7 @@ bool NotificationsManager::shouldPostNotification(QPointer<Quotient::Connection>
|
||||
|
||||
// If the notification timestamp is earlier than the initial timestamp assume
|
||||
// the notification is old and shouldn't be posted.
|
||||
qint64 timestamp = notification["ts"_ls].toDouble();
|
||||
qint64 timestamp = notification["ts"].toDouble();
|
||||
if (timestamp < m_initialTimestamp[connection->user()->id()]) {
|
||||
return false;
|
||||
}
|
||||
@@ -185,7 +187,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
const QString roomId = room->id();
|
||||
KNotification *notification = m_notifications.value(roomId);
|
||||
if (!notification) {
|
||||
notification = new KNotification(QStringLiteral("message"));
|
||||
notification = new KNotification("message");
|
||||
m_notifications.insert(roomId, notification);
|
||||
connect(notification, &KNotification::closed, this, [this, roomId] {
|
||||
m_notifications.remove(roomId);
|
||||
@@ -201,7 +203,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
|
||||
entry = i18n("%1: %2", sender, text.toHtmlEscaped());
|
||||
}
|
||||
|
||||
notification->setText(notification->text() + QLatin1Char('\n') + entry);
|
||||
notification->setText(notification->text() + '\n' + entry);
|
||||
notification->setPixmap(createNotificationImage(icon, room));
|
||||
|
||||
notification->setDefaultAction(i18n("Open NeoChat in this room"));
|
||||
@@ -235,7 +237,7 @@ void NotificationsManager::postInviteNotification(NeoChatRoom *room, const QStri
|
||||
{
|
||||
QPixmap img;
|
||||
img.convertFromImage(icon);
|
||||
KNotification *notification = new KNotification(QStringLiteral("invite"));
|
||||
KNotification *notification = new KNotification("invite");
|
||||
notification->setText(i18n("%1 invited you to a room", sender));
|
||||
notification->setTitle(title);
|
||||
notification->setPixmap(createNotificationImage(icon, nullptr));
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user