Compare commits

..

4 Commits

Author SHA1 Message Date
Tobias Fella
604c652821 Use Quotient qml module 2025-09-15 16:48:44 +02:00
Tobias Fella
cc2daa2021 Use PageStack.closeDialog attached function 2025-09-15 16:47:55 +02:00
Tobias Fella
e2de5b5f00 Fix qml warnings in key verification 2025-09-15 16:47:21 +02:00
Tobias Fella
1896d69253 Modernize PropertyChanges 2025-09-15 16:15:08 +02:00
158 changed files with 30009 additions and 52923 deletions

View File

@@ -2,7 +2,7 @@
"id": "org.kde.neochat",
"branch": "master",
"runtime": "org.kde.Platform",
"runtime-version": "6.10",
"runtime-version": "6.9",
"sdk": "org.kde.Sdk",
"command": "neochat",
"tags": [
@@ -31,6 +31,19 @@
"/share/ndk-modules"
],
"modules": [
{
"name": "kirigamiaddons",
"config-opts": [
"-DBUILD_TESTING=OFF"
],
"buildsystem": "cmake-ninja",
"sources": [
{
"type": "git",
"url": "https://invent.kde.org/libraries/kirigami-addons.git"
}
]
},
{
"name": "opencv",
"config-opts": [
@@ -65,7 +78,6 @@
"name": "olm",
"buildsystem": "cmake-ninja",
"config-opts": [
"-DCMAKE_POLICY_VERSION_MINIMUM=3.5",
"-DOLM_TESTS=OFF"
],
"sources": [
@@ -172,8 +184,8 @@
"sources": [
{
"type": "archive",
"url": "https://download.kde.org/stable/release-service/25.08.3/src/kunifiedpush-25.08.3.tar.xz",
"sha256": "e8c924438d5359f0fa0930ab35111012076e3a0ff4e959d6929595571383320a",
"url": "https://download.kde.org/stable/release-service/25.08.0/src/kunifiedpush-25.08.0.tar.xz",
"sha256": "846db6ffc7d93f6afea7ce0d5a9f10b52792157ceb593856542279f4197f3518",
"x-checker-data": {
"type": "anitya",
"project-id": 8763,

View File

@@ -14,6 +14,7 @@ Dependencies:
'frameworks/kquickcharts': '@latest-kf6'
'frameworks/knotifications': '@latest-kf6'
'frameworks/kcolorscheme': '@latest-kf6'
'frameworks/kiconthemes': '@latest-kf6'
'libraries/kquickimageeditor': '@latest-kf6'
'frameworks/sonnet': '@latest-kf6'
'frameworks/prison': '@latest-kf6'
@@ -28,15 +29,12 @@ Dependencies:
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD', 'Android']
'require':
'libraries/kunifiedpush': '@latest-kf6'
'frameworks/kcrash': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@latest-kf6'
'frameworks/purpose': '@latest-kf6'
'libraries/kunifiedpush': '@latest-kf6'
- 'on': ['Linux']
'require':

View File

@@ -7,8 +7,8 @@
cmake_minimum_required(VERSION 3.16)
# KDE Applications version, managed by release script.
set(RELEASE_SERVICE_VERSION_MAJOR "26")
set(RELEASE_SERVICE_VERSION_MINOR "03")
set(RELEASE_SERVICE_VERSION_MAJOR "25")
set(RELEASE_SERVICE_VERSION_MINOR "11")
set(RELEASE_SERVICE_VERSION_MICRO "70")
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
@@ -24,7 +24,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 6.17)
set(KDE_COMPILERSETTINGS_LEVEL 6.0)
include(FeatureSummary)
include(ECMSetupVersion)
@@ -39,16 +39,17 @@ include(ECMCheckOutboundLicense)
include(ECMQtDeclareLoggingCategory)
include(ECMAddAndroidApk)
include(ECMQmlModule)
include(ECMDeprecationSettings)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
if (NOT ANDROID)
include(KDEClangFormat)
endif()
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
if(NEOCHAT_FLATPAK)
include(cmake/Flatpak.cmake)
endif()
ecm_set_disabled_deprecation_versions(Qt 6.9.0 KF 6.17.0)
set(QUOTIENT_FORCE_NAMESPACED_INCLUDES TRUE)
ecm_setup_version(${PROJECT_VERSION}
VARIABLE_PREFIX NEOCHAT
@@ -65,7 +66,7 @@ if (QT_KNOWN_POLICY_QTP0004)
qt_policy(SET QTP0004 NEW)
endif ()
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels ColorScheme)
find_package(KF6 ${KF_MIN_VERSION} COMPONENTS Kirigami I18n Notifications Config CoreAddons Sonnet ItemModels IconThemes ColorScheme)
set_package_properties(KF6 PROPERTIES
TYPE REQUIRED
PURPOSE "Basic application components"
@@ -74,7 +75,7 @@ set_package_properties(KF6Kirigami PROPERTIES
TYPE REQUIRED
PURPOSE "Kirigami application UI framework"
)
find_package(KF6KirigamiAddons 1.10.0 REQUIRED)
find_package(KF6KirigamiAddons 1.6.0 REQUIRED)
if (UNIX AND NOT APPLE AND NOT ANDROID AND NOT NEOCHAT_FLATPAK AND NOT NEOCHAT_APPIMAGE)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS Purpose)
@@ -88,7 +89,7 @@ if(ANDROID)
)
else()
find_package(Qt6 ${QT_MIN_VERSION} COMPONENTS Widgets)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem)
find_package(KF6 ${KF_MIN_VERSION} REQUIRED COMPONENTS QQC2DesktopStyle KIO WindowSystem StatusNotifierItem Crash)
find_package(KF6SyntaxHighlighting ${KF_MIN_VERSION} REQUIRED)
set_package_properties(KF6QQC2DesktopStyle PROPERTIES
TYPE RUNTIME
@@ -148,7 +149,7 @@ set_package_properties(KF6DocTools PROPERTIES DESCRIPTION
option(WITH_UNIFIEDPUSH "Build with KUnifiedPush support" ON)
if (APPLE OR WIN32 OR HAIKU)
if (ANDROID OR APPLE OR WIN32 OR HAIKU)
set(WITH_UNIFIEDPUSH OFF)
endif()

View File

@@ -4,12 +4,15 @@
#include "server.h"
#include <QFile>
#include <QHttpServer>
#include <QHttpServerResponder>
#include <QJsonArray>
#include <QJsonDocument>
#include <QJsonObject>
#include <QNetworkReply>
#include <QSslCertificate>
#include <QSslKey>
#include <QSslServer>
#include <QUuid>
#include <Quotient/networkaccessmanager.h>

14
cmake/Flatpak.cmake Normal file
View File

@@ -0,0 +1,14 @@
# SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
# SPDX-License-Identifier: BSD-2-Clause
include(GNUInstallDirs)
# Include FontConfig config which uses the Emoji One font from the
# KDE Flatpak SDK.
install(
FILES
${CMAKE_CURRENT_SOURCE_DIR}/cmake/Flatpak/99-noto-mono-color-emoji.conf
DESTINATION
${CMAKE_INSTALL_SYSCONFDIR}/fonts/local.conf
)

View File

@@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<alias>
<family>serif</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<alias>
<family>sans-serif</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
<alias>
<family>monospace</family>
<prefer>
<family>Noto Color Emoji</family>
</prefer>
</alias>
</fontconfig>

View File

@@ -41,7 +41,6 @@
<name xml:lang="pl">NeoChat</name>
<name xml:lang="pt">NeoChat</name>
<name xml:lang="pt-BR">NeoChat</name>
<name xml:lang="ro">NeoChat</name>
<name xml:lang="ru">NeoChat</name>
<name xml:lang="sa">नवचैट्</name>
<name xml:lang="sk">NeoChat</name>
@@ -76,7 +75,6 @@
<summary xml:lang="nn">Prat med via Matrix</summary>
<summary xml:lang="pl">Rozmawiaj na Matriksie</summary>
<summary xml:lang="pt-BR">Bate-papo na Matrix</summary>
<summary xml:lang="ro">Discutați pe Matrix</summary>
<summary xml:lang="ru">Общение в Matrix</summary>
<summary xml:lang="sa">Matrix इत्यत्र गपशपं कुर्वन्तु</summary>
<summary xml:lang="sl">Klepet na Matrixu</summary>
@@ -111,7 +109,6 @@
<p xml:lang="nn">NeoChat er ein prateapp som lèt deg bruka all funksjonalitet i Matrix-nettverket. Du kan utveksla tekst, lyd og videoar med vennar, familie og kollegaar på ein trygg måte.</p>
<p xml:lang="pl">NoeChat to aplikacja do rozmów, która umożliwia wykorzystanie wszystkich możliwości Matriksa. Umożliwia wysyłanie wiadomości tekstowych, filmów i dźwięków w bezpieczny sposób do twojej rodziny, kolegów i przyjaciół.</p>
<p xml:lang="pt-BR">O NeoChat é um aplicativo de bate-papo que permite que você aproveite ao máximo a rede Matrix. Ele oferece uma maneira segura de enviar mensagens de texto, vídeos e arquivos de áudio para sua família, colegas e amigos.</p>
<p xml:lang="ro">NeoChat e o aplicație de discuții ce vă ajută să profitați din plin de rețeaua Matrix. Aceasta oferă o modalitate sigură de a trimite mesaje textuale, videoclipuri și fișiere audio familiei, colegilor și prietenilor.</p>
<p xml:lang="ru">NeoChat — приложение для общения, предоставляющее все преимущества сети Matrix. С его помощью можно безопасно отправлять текстовые сообщения, видеозаписи и звуковые файлы родственникам, коллегам и друзьям.</p>
<p xml:lang="sa">NeoChat इति एकं गपशप-अनुप्रयोगं यत् भवान् Matrix-जालस्य पूर्णं लाभं ग्रहीतुं शक्नोति । एतत् भवन्तं भवतः परिवाराय, सहकारिभ्यः, मित्रेभ्यः च पाठसन्देशान्, भिडियो, श्रव्यसञ्चिकाः च प्रेषयितुं सुरक्षितं मार्गं प्रदाति ।</p>
<p xml:lang="sl">NeoChat je aplikacija za klepet, ki vam omogoča, da v celoti izkoristite omrežje Matrix. Zagotavlja vam varen način za pošiljanje besedilnih sporočil, videoposnetkov in zvočnih datotek vaši družini, sodelavcem in prijateljem.</p>
@@ -145,7 +142,6 @@
<p xml:lang="pl">NeoChat w zamyśle ma być pełnowartościową aplikacją wg wytycznych Matriksa. Z tego powodu, wszystko, co jest obecnie w stabilnych wytycznych z pominięciem VoIP, wątków i niektórych części szyfrowania Użytkownik-do-Użytkownika są obecnie obsługiwane. Pominięto też kilka mniejszych rzeczy ze względu na ciągły rozwój wytycznych Matriksa, lecz celem nadal jest zapewnienie obsługi wszystkich wytycznych.</p>
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
<p xml:lang="pt-BR">O NeoChat pretende ser um aplicativo completo para a especificação Matrix. Dessa forma, tudo na especificação estável atual, com as notáveis exceções de VoIP, tópicos e alguns aspectos da criptografia de ponta a ponta, é suportado. Há algumas outras pequenas omissões devido ao fato de a especificação Matrix estar em constante evolução, mas o objetivo continua sendo fornecer suporte eventual para toda a especificação.</p>
<p xml:lang="ro">NeoChat vrea să fie o aplicație completă pentru specificațiile Matrix. Astfel, susține tot ce se găsește acum în specificațiile stabile cu excepția VoIP, a firelor de discuții, și a unor părți din criptarea punct-la-punct. Sunt și câteva omisiuni minore din cauza faptului că specificația Matrix evoluează continuu, dar scopul rămâne acela de a implementa întreaga specificație.</p>
<p xml:lang="ru">Целью создания NeoChat является полноценная реализация программы для спецификации Matrix. Как следствие, реализовано всё в текущей стабильной спецификации (за исключением голосовой интернет-связи, потоков и некоторых аспектов сквозного шифрования). Есть также несколько других незначительных пробелов, обусловленных постоянными изменениями спецификации Matrix. Тем не менее, стоит задача в итоге предоставить полную поддержку спецификации.</p>
<p xml:lang="sa">NeoChat इत्यस्य उद्देश्यं Matrix विनिर्देशस्य कृते पूर्णतया विशेषतायुक्तः अनुप्रयोगः भवितुम् अस्ति । यथा तथा वर्तमानस्थिरविनिर्देशे सर्वं VoIP इत्यस्य उल्लेखनीयअपवादैः सह, थ्रेड्स तथा च End-to-End Encryption इत्यस्य केचन पक्षाः समर्थिताः सन्ति । अन्ये कतिचन लघु लोपाः सन्ति यतोहि Matrix spec निरन्तरं विकसितः अस्ति परन्तु उद्देश्यं सम्पूर्ण spec कृते अन्ततः समर्थनं प्रदातुं अवशिष्टम् अस्ति</p>
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
@@ -179,7 +175,6 @@
<p xml:lang="pl">Ze względu na sposób rozwoju Matriksa, NeoChat obsługuje także kilka niestabilnych możliwości. Obecnie są to:</p>
<p xml:lang="pt">Devido à natureza do desenvolvimento da especificação do Matrix, o NeoChat também suporta diversas funcionalidades instáveis. De momento são:</p>
<p xml:lang="pt-BR">Devido à natureza do desenvolvimento da especificação Matrix, o NeoChat também suporta diversos recursos instáveis. Atualmente, são eles:</p>
<p xml:lang="ro">Datorită modului de dezvoltare a specificațiilor Matrix, NeoChat susține și numeroase caracteristici nestabile. Acum, acestea sunt:</p>
<p xml:lang="ru">В силу природы разработки спецификации Matrix в NeoChat тоже предусмотрена поддержка многочисленных нестабильных возможностей. В текущей версии это следующие возможности:</p>
<p xml:lang="sa">Matrix विनिर्देशविकासस्य प्रकृतेः कारणात् NeoChat अपि अनेकानाम् अस्थिरविशेषतानां समर्थनं करोति । सम्प्रति एते सन्ति :</p>
<p xml:lang="sl">Zaradi narave razvoja specifikacije Matrixa NeoChat podpira tudi številne nestabilne zmožnosti. Trenutno so to:</p>
@@ -214,7 +209,6 @@
<li xml:lang="pl">Ankiety - MSC3381</li>
<li xml:lang="pt">Inquéritos - MSC3381</li>
<li xml:lang="pt-BR">Enquetes - MSC3381</li>
<li xml:lang="ro">Sondaje - MSC3381</li>
<li xml:lang="ru">Голосования — MSC3381</li>
<li xml:lang="sa">मतदान - MSC3381</li>
<li xml:lang="sl">Polls - MSC3381</li>
@@ -248,7 +242,6 @@
<li xml:lang="pl">Paczki naklejek - MSC2545</li>
<li xml:lang="pt">Pacotes de Autocolantes - MSC2545</li>
<li xml:lang="pt-BR">Pacotes de Stickers - MSC2545</li>
<li xml:lang="ro">Colecții de abțibilduri - MSC2545</li>
<li xml:lang="ru">Наборы стикеров — MSC2545</li>
<li xml:lang="sa">स्टिकर पैक - MSC2545</li>
<li xml:lang="sl">Sticker Packs - MSC2545</li>
@@ -282,7 +275,6 @@
<li xml:lang="pl">Wydarzenia w miejscach - MSC3488</li>
<li xml:lang="pt">Eventos com Localizações - MSC3488</li>
<li xml:lang="pt-BR">Localização de eventos - MSC3488</li>
<li xml:lang="ro">Evenimente de amplasare - MSC3488</li>
<li xml:lang="ru">События местоположения — MSC3488</li>
<li xml:lang="sa">स्थान घटनाएँ - MSC3488</li>
<li xml:lang="sl">Location Events - MSC3488</li>
@@ -320,6 +312,7 @@
<value key="KDE::windows_store::StoreLogoSquare">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/storelogo-1080x1080.png</value>
<value key="KDE::windows_store::Icon">https://invent.kde.org/network/neochat/-/raw/master/icons/300-apps-neochat.png</value>
<value key="KDE::windows_store::PromotionalArt16x9">https://invent.kde.org/network/neochat/-/raw/master/icons/windows/promoimage-1920x1080.png</value>
<value key="KDE::supporters">Tanguy Fardet;[dabe](https://freeradical.zone/@dabe);[lengau](https://mastodon.world/@lengau);Joshua Strobl;Stuart Turton</value>
</custom>
<launchable type="desktop-id">org.kde.neochat.desktop</launchable>
<screenshots>
@@ -351,7 +344,6 @@
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
@@ -388,7 +380,6 @@
<caption xml:lang="nn">Oppdag nye fellesskap med Matrix Spaces</caption>
<caption xml:lang="pl">Odkrywaj nowe społeczności w Przestrzeniach Matriksa</caption>
<caption xml:lang="pt-BR">Descubra novas comunidades com os Espaços Matrix</caption>
<caption xml:lang="ro">Descoperiți comunități noi cu Spații Matrix</caption>
<caption xml:lang="ru">Поиск новых сообществ с помощью Matrix Spaces</caption>
<caption xml:lang="sa">Matrix Spaces इत्यनेन सह नूतनानां समुदायानाम् अन्वेषणं कुर्वन्तु</caption>
<caption xml:lang="sl">Odkrijte nove skupnosti z Matrix Spaces</caption>
@@ -433,7 +424,6 @@
<caption xml:lang="pl">Główny widok z wykazem pokojów, rozmowami i szczegółami pokojów</caption>
<caption xml:lang="pt">A área principal com a lista de salas e com informações sobre a conversa e a sala</caption>
<caption xml:lang="pt-BR">Visão principal com lista de salas, bate-papo e informações sobre as salas</caption>
<caption xml:lang="ro">Vederea principală cu lista de camere, discuție, și informații despre cameră</caption>
<caption xml:lang="ru">Главное окно со списком комнат, чатом и информацией о комнате</caption>
<caption xml:lang="sa">कक्षसूची, गपशपः, कक्षसूचना च सह मुख्यदृश्यम्</caption>
<caption xml:lang="sl">Glavni pogled s seznamom sob, klepetom in informacijami o sobah</caption>
@@ -472,7 +462,6 @@
<caption xml:lang="pl">Ekran logowania</caption>
<caption xml:lang="pt">Ecrã de autenticação</caption>
<caption xml:lang="pt-BR">Tela de login</caption>
<caption xml:lang="ro">Ecran de autentificare</caption>
<caption xml:lang="ru">Окно входа</caption>
<caption xml:lang="sa">लॉगिन् स्क्रीन</caption>
<caption xml:lang="sl">Prijavni zaslon</caption>
@@ -487,9 +476,6 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="25.12.0" date="2025-12-11"/>
<release version="25.08.3" date="2025-11-06"/>
<release version="25.08.2" date="2025-10-09"/>
<release version="25.08.1" date="2025-09-11"/>
<release version="25.08.0" date="2025-08-14"/>
<release version="25.04.3" date="2025-07-03"/>

View File

@@ -112,7 +112,6 @@ Comment[lv]=Tērzējiet „Matrix“ tīklā
Comment[nl]=Chat op Matrix
Comment[pl]=Rozmawiaj na Matriksie
Comment[pt_BR]=Bate papo na Matrix
Comment[ro]=Discutați pe Matrix
Comment[ru]=Общение в Matrix
Comment[sa]=Matrix इत्यत्र गपशपं कुर्वन्तु
Comment[sl]=Klepet na Matrixu

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,122 +0,0 @@
<?xml version="1.0" ?>
<!DOCTYPE refentry PUBLIC "-//KDE//DTD DocBook XML V4.5-Based Variant V1.1//EN" "dtd/kdedbx45.dtd" [
<!ENTITY % Brazilian-Portuguese "INCLUDE">
]>
<!--
SPDX-FileCopyrightText: 2022 Carl Schwan <carl@carlschwan.eu>
SPDX-License-Identifier: CC-BY-SA-4.0
-->
<refentry lang="&language;">
<refentryinfo>
<title
>Manual do Usuário do NeoChat</title>
<author
><firstname
>Carl</firstname
><surname
>Schwan</surname
> <contrib
>NeoChat man page.</contrib
> <email
>carl@carlschwan.eu</email
></author>
<date
>01/11/2022</date>
<releaseinfo
>22.09</releaseinfo>
<productname
>NeoChat</productname>
</refentryinfo>
<refmeta>
<refentrytitle>
<command
>neochat</command>
</refentrytitle>
<manvolnum
>1</manvolnum>
</refmeta>
<refnamediv>
<refname
>neochat</refname>
<refpurpose
>Cliente para interação com o protocolo de mensagens Matrix.</refpurpose>
</refnamediv>
<!-- body begins here -->
<refsynopsisdiv id='synopsis'>
<cmdsynopsis
><command
>neochat</command
> <arg choice="opt"
><replaceable
>URI</replaceable
></arg
> </cmdsynopsis>
</refsynopsisdiv>
<refsect1 id="description">
<title
>Descrição</title>
<para
>O <command
>neochat</command
> é um aplicativo de bate-papo para o protocolo Matrix. Ele funciona tanto em computadores quanto em dispositivos móveis. </para>
</refsect1>
<refsect1 id="options"
><title
>Opções</title>
<variablelist>
<varlistentry>
<term
><option
>URI</option
></term>
<listitem>
<para
>O URI da matriz para um usuário ou uma sala. Por exemplo, matrix:u/usuário:exemplo.org e matrix:r/root:exemplo.org. Isso fará com que o NeoChat tente abrir a sala ou conversa especificada. </para>
</listitem>
</varlistentry>
</variablelist>
</refsect1>
<refsect1 id="bug">
<title
>Relatar bugs</title>
<para
>Você pode reportar erros e solicitar novas funcionalidades em <ulink url="https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General"
>https://bugs.kde.org/enter_bug.cgi?product=NeoChat&amp;component=General</ulink
></para>
</refsect1>
<refsect1>
<title
>Veja também</title>
<simplelist>
<member
>Lista de perguntas frequentes sobre o Matrix <ulink url="https://matrix.org/faq/"
>https://matrix.org/faq/</ulink
> </member>
<member
>kf5options(7)</member>
<member
>qt5options(7)</member>
</simplelist>
</refsect1>
<refsect1 id="copyright"
><title
>Direitos autorais</title>
<para
>Direitos autorais &copy; 2020-2022 Tobias Fella </para>
<para
>Direitos autorais &copy; 2020-2022 Carl Schwan </para>
<para
>Licença: GNU General Public Versão 3 ou posterior <ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink
>&gt;</para>
</refsect1>
</refentry>

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -103,7 +103,6 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
qml/ReasonDialog.qml
qml/NewPollDialog.qml
qml/UserMenu.qml
qml/MeetingDialog.qml
DEPENDENCIES
QtCore
QtQuick
@@ -203,9 +202,8 @@ target_link_libraries(neochat PUBLIC
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::IconThemes
KF6::ItemModels
KF6::I18nQml
KirigamiApp
QuotientQt6
Login
Rooms
@@ -213,6 +211,10 @@ target_link_libraries(neochat PUBLIC
Spaces
)
if (TARGET KF6::Crash)
target_link_libraries(neochat PUBLIC KF6::Crash)
endif()
kconfig_target_kcfg_file(neochat FILE neochatconfig.kcfg CLASS_NAME NeoChatConfig MUTATORS GENERATE_PROPERTIES DEFAULT_VALUE_GETTERS PARENT_IN_CONSTRUCTOR SINGLETON GENERATE_MOC QML_REGISTRATION)
if(NEOCHAT_FLATPAK)
@@ -323,7 +325,6 @@ if(ANDROID)
"kt-restore-defaults-symbolic"
"user-symbolic"
"mark-location-symbolic"
"amarok_playcount"
${KIRIGAMI_ADDONS_ICONS}
)
@@ -352,8 +353,7 @@ endif()
install(TARGETS neochat-app ${KDE_INSTALL_TARGETS_DEFAULT_ARGS})
if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
# krunner plugin must be the same as the app id for flatpak to export it
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins RENAME org.kde.neochat.desktop)
install(FILES plasma-runner-neochat.desktop DESTINATION ${KDE_INSTALL_DATAROOTDIR}/krunner/dbusplugins)
endif()
if (APPLE)

View File

@@ -17,6 +17,7 @@
#include <Quotient/qt_connection_util.h>
#include <Quotient/settings.h>
#include "accountmanager.h"
#include "enums/roomsortparameter.h"
#include "general_logging.h"
#include "mediasizehelper.h"
@@ -25,7 +26,9 @@
#include "models/roomlistmodel.h"
#include "models/roomtreemodel.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
#include "notificationsmanager.h"
#include "proxycontroller.h"
#include "roommanager.h"
@@ -246,10 +249,7 @@ void Controller::initActiveConnection(NeoChatConnection *oldConnection, NeoChatC
if (newConnection) {
connect(newConnection, &NeoChatConnection::errorOccured, this, &Controller::errorOccured);
connect(newConnection, &NeoChatConnection::badgeNotificationCountChanged, this, &Controller::updateBadgeNotificationCount);
// Refresh and update manually, in case we init too late for the badge count to actually change.
newConnection->refreshBadgeNotificationCount();
updateBadgeNotificationCount(newConnection->badgeNotificationCount());
}
Q_EMIT activeConnectionChanged(newConnection);
}
@@ -309,7 +309,8 @@ void Controller::listenForNotifications()
connect(timer, &QTimer::timeout, qGuiApp, &QGuiApplication::quit);
connect(connector, &KUnifiedPush::Connector::messageReceived, [timer](const QByteArray &data) {
NotificationsManager::postPushNotification(data);
instance().m_notificationsManager.postPushNotification(data);
timer->stop();
});
// Wait five seconds to see if we received any messages or this happened to be an erroneous activation.

View File

@@ -33,9 +33,13 @@
#include <KWindowSystem>
#endif
#include <KLocalizedQmlContext>
#if __has_include("KCrash")
#include <KCrash>
#endif
#include <KIconTheme>
#include <KLocalizedContext>
#include <KLocalizedString>
#include <KirigamiApp>
#include "neochat-version.h"
@@ -100,6 +104,7 @@ Q_DECL_EXPORT
#endif
int main(int argc, char *argv[])
{
KIconTheme::initTheme();
QNetworkProxyFactory::setUseSystemConfiguration(true);
#ifdef HAVE_WEBVIEW
@@ -108,10 +113,24 @@ int main(int argc, char *argv[])
QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGLRhi);
#endif
KirigamiApp::App app(argc, argv);
KirigamiApp kirigamiApp;
#ifdef Q_OS_ANDROID
QGuiApplication app(argc, argv);
QQuickStyle::setStyle(u"org.kde.breeze"_s);
#else
QIcon::setFallbackThemeName("breeze"_L1);
QApplication app(argc, argv);
// Default to org.kde.desktop style unless the user forces another style
if (qEnvironmentVariableIsEmpty("QT_QUICK_CONTROLS_STYLE")) {
QQuickStyle::setStyle(u"org.kde.desktop"_s);
}
#endif
#ifdef Q_OS_WINDOWS
if (AttachConsole(ATTACH_PARENT_PROCESS)) {
freopen("CONOUT$", "w", stdout);
freopen("CONOUT$", "w", stderr);
}
QApplication::setStyle(u"breeze"_s);
QFont font(u"Segoe UI Emoji"_s);
font.setPointSize(10);
@@ -158,9 +177,19 @@ int main(int argc, char *argv[])
KAboutData::setApplicationData(about);
QGuiApplication::setWindowIcon(QIcon::fromTheme(u"org.kde.neochat"_s));
#if __has_include("KCrash")
KCrash::initialize();
#endif
Connection::setEncryptionDefault(true);
Connection::setDirectChatEncryptionDefault(true);
#ifdef NEOCHAT_FLATPAK
// Copy over the included FontConfig configuration to the
// app's config dir:
QFile::copy(u"/app/etc/fonts/conf.d/99-noto-mono-color-emoji.conf"_s, u"/var/config/fontconfig/conf.d/99-noto-mono-color-emoji.conf"_s);
#endif
ColorSchemer colorScheme;
QCommandLineParser parser;
@@ -176,7 +205,7 @@ int main(int argc, char *argv[])
parser.addOption(testOption);
#ifdef HAVE_KUNIFIEDPUSH
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s);
QCommandLineOption dbusActivatedOption(u"dbus-activated"_s, i18n("Internal usage only."));
dbusActivatedOption.setFlags(QCommandLineOption::Flag::HiddenFromHelp);
parser.addOption(dbusActivatedOption);
#endif
@@ -190,14 +219,8 @@ int main(int argc, char *argv[])
#ifdef HAVE_KUNIFIEDPUSH
if (parser.isSet(dbusActivatedOption)) {
#ifdef HAVE_KDBUSADDONS
// We *don't* want to use KDBusService here. I don't know why, but it makes activation super unreliable. We don't really need it anyway.
if (!QDBusConnection::sessionBus().registerService(QStringLiteral("org.kde.neochat"))) {
// Gracefully fail if NeoChat is already running
qWarning() << "NeoChat already running, not sending push notifications.";
return 0;
}
#endif
// We want to be replaceable by the main client
KDBusService service(KDBusService::Replace);
#ifdef HAVE_RUNNER
// If we are built with KRunner and KUnifiedPush support, we need to do something special.
@@ -256,7 +279,7 @@ int main(int argc, char *argv[])
});
#endif
KLocalization::setupLocalizedContext(&engine);
engine.rootContext()->setContextObject(new KLocalizedContext(&engine));
engine.setNetworkAccessManagerFactory(new NetworkAccessManagerFactory());
if (parser.isSet("ignore-ssl-errors"_L1)) {
@@ -271,9 +294,7 @@ int main(int argc, char *argv[])
engine.addImageProvider(u"blurhash"_s, new BlurhashImageProvider);
if (!kirigamiApp.start("org.kde.neochat", "Main", &engine)) {
return -1;
}
engine.loadFromModule("org.kde.neochat", "Main");
if (!parser.positionalArguments().isEmpty() && !parser.isSet("share"_L1)) {
RoomManager::instance().setUrlArgument(parser.positionalArguments()[0]);

View File

@@ -211,7 +211,6 @@ Name[pa]=ਨਵਾਂ ਸੱਦਾ
Name[pl]=Nowe zaproszenie
Name[pt]=Novo Convite
Name[pt_BR]=Novo convite
Name[ro]=Invitație nouă
Name[ru]=Новое приглашение
Name[sa]=नवीन आमन्त्रणम्
Name[sl]=Novo povabilo
@@ -253,13 +252,12 @@ Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
Comment[pl]=Dostępna jest nowe zaproszenie do pokoju
Comment[pt]=Existe um novo convite para uma sala
Comment[pt_BR]=Existe um novo convite para uma sala
Comment[ro]=E o nouă invitație la o cameră
Comment[ru]=Доступно новое приглашение в комнату
Comment[sa]=कक्षस्य नूतनं निमन्त्रणम् अस्ति
Comment[sl]=Tam je novo povabilo v sobo
Comment[sv]=Det finns en ny inbjudan till ett rum
Comment[ta]=ஓர் அரங்கிற்கான புதிய அழைப்பிதழ் உள்ளது
Comment[tr]=Bir odaya yeni bir davet var
Comment[tr]=Bir odaya yeni bir davetiye var
Comment[uk]=У кімнаті нове запрошення
Comment[zh_CN]=有新的聊天室邀请
Comment[zh_TW]=有新的加入聊天室邀請
@@ -292,7 +290,6 @@ Name[nl]=Gedeelde
Name[nn]=Del
Name[pl]=Udostępnij
Name[pt_BR]=Compartilhar
Name[ro]=Partajare
Name[ru]=Публикация
Name[sa]=संविभागः
Name[sl]=Deli
@@ -327,7 +324,6 @@ Comment[nl]=Het resultaat van het delen van een stukje inhoud
Comment[nn]=Resultatet av deling av innhald
Comment[pl]=Wynik udostępniania kawałka treści
Comment[pt_BR]=O resultado de compartilhar um conteúdo
Comment[ro]=Rezultatul partajării unei bucăți de conținut
Comment[ru]=Результат публикации данных
Comment[sa]=सामग्रीखण्डस्य साझाकरणस्य परिणामः
Comment[sl]=Rezultat deljenega kosa vsebine

View File

@@ -66,10 +66,6 @@
</entry>
</group>
<group name="Timeline">
<entry name="FontScale" type="double">
<label>Scaling factor for font sizes</label>
<default>1.0</default>
</entry>
<entry name="ShowAvatarInTimeline" type="bool">
<label>Show avatar in the timeline</label>
<default>true</default>
@@ -211,6 +207,10 @@
<label>Enable threads</label>
<default>false</default>
</entry>
<entry name="SecretBackup" type="bool">
<label>Enable secret backup</label>
<default>false</default>
</entry>
<entry name="Phone3PId" type="bool">
<label>Enable add phone numbers as 3PIDs</label>
<default>false</default>

View File

@@ -388,7 +388,7 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
#ifdef HAVE_KIO
auto openAction = notification->addAction(i18n("Open NeoChat"));
connect(openAction, &KNotificationAction::activated, notification, [=]() {
connect(openAction, &KNotificationAction::activated, this, [=]() {
QString properId = roomId;
properId = properId.replace(u"#"_s, QString());
properId = properId.replace(u"!"_s, QString());
@@ -402,6 +402,8 @@ void NotificationsManager::postPushNotification(const QByteArray &message)
connect(notification, &KNotification::closed, qGuiApp, &QGuiApplication::quit);
notification->sendEvent();
m_notifications.insert(roomId, {json["ts"_L1].toVariant().toLongLong(), notification});
} else {
qWarning() << "Skipping unsupported push notification" << type;
}

View File

@@ -53,7 +53,7 @@ public:
/**
* @brief Display a native notification for the given push notification.
*/
static void postPushNotification(const QByteArray &message);
void postPushNotification(const QByteArray &message);
/**
* @brief Handle the notifications for the given connection.

View File

@@ -75,7 +75,6 @@ Comment[nn]=Finn rom i NeoChat
Comment[pl]=Znajdź pokoje w NeoChat
Comment[pt]=Procurar salas no NeoChat
Comment[pt_BR]=Encontrar salas no NeoChat
Comment[ro]=Găsește camere în NeoChat
Comment[ru]=Поиск комнат NeoChat
Comment[sa]=NeoChat इत्यत्र कक्ष्याः अन्वेषणं कुर्वन्तु
Comment[sl]=Najdi sobe v NeoChatu

View File

@@ -79,6 +79,7 @@ KirigamiComponents.ConvergentContextMenu {
Kirigami.Action {
text: i18nc("@action:inmenu", "Open Secret Backup")
icon.name: "unlock"
visible: NeoChatConfig.secretBackup
onTriggered: root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UnlockSSSSDialog'), {}, {
title: i18nc("@title:window", "Open Key Backup")
})

View File

@@ -91,7 +91,6 @@ Components.AbstractMaximizeComponent {
color: Kirigami.Theme.textColor
font.family: "monospace"
font.pointSize: Kirigami.Theme.defaultFont.pointSize * NeoChatConfig.fontScale
Kirigami.SpellCheck.enabled: false

View File

@@ -22,12 +22,12 @@ Labs.MenuBar {
Labs.MenuItem {
icon.name: "list-add-user"
text: i18nc("@action:inmenu", "Find User")
text: i18nc("@action:inmenu", "Find your Friends")
enabled: root.connection
onTriggered: root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'UserSearchPage'), {
connection: root.connection
}, {
title: i18nc("@title", "Find User")
title: i18nc("@title", "Find your friends")
})
}
Labs.MenuItem {

View File

@@ -54,12 +54,6 @@ ColumnLayout {
Layout.alignment: Qt.AlignHCenter
}
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
Kirigami.SelectableLabel {
Layout.fillWidth: true
font: Kirigami.Theme.smallFont
@@ -67,7 +61,12 @@ ColumnLayout {
visible: root.currentRoom && root.currentRoom.canonicalAlias
text: root.currentRoom && root.currentRoom.canonicalAlias ? root.currentRoom.canonicalAlias : ""
color: Kirigami.Theme.disabledTextColor
horizontalAlignment: Text.AlignHCenter
}
Kirigami.Heading {
text: root.currentRoom.displayName
Layout.alignment: Qt.AlignHCenter
}
}
}

View File

@@ -1,6 +1,8 @@
// SPDX-FileCopyrightText: 2022 Tobias Fella <tobias.fella@kde.org>
// SPDX-License-Identifier: GPL-2.0-or-later
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
@@ -8,8 +10,8 @@ import QtQuick.Window
import QtQml
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat
import io.github.quotient_im.libquotient
Kirigami.Page {
id: root
@@ -23,72 +25,63 @@ Kirigami.Page {
name: "cancelled"
when: root.session.state === KeyVerificationSession.CANCELED
PropertyChanges {
target: stateLoader
sourceComponent: verificationCanceled
stateLoader.sourceComponent: verificationCanceled
}
},
State {
name: "waitingForVerification"
when: root.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
PropertyChanges {
target: stateLoader
sourceComponent: emojiSas
stateLoader.sourceComponent: emojiSas
}
},
State {
name: "waitingForReady"
when: root.session.state === KeyVerificationSession.WAITINGFORREADY
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "incoming"
when: root.session.state === KeyVerificationSession.INCOMING
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "waitingForKey"
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "waitingForAccept"
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "waitingForMac"
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
},
State {
name: "ready"
when: root.session.state === KeyVerificationSession.READY
PropertyChanges {
target: stateLoader
sourceComponent: chooseVerificationComponent
stateLoader.sourceComponent: chooseVerificationComponent
}
},
State {
name: "done"
when: root.session.state === KeyVerificationSession.DONE
PropertyChanges {
target: stateLoader
sourceComponent: message
stateLoader.sourceComponent: message
}
}
]
@@ -172,7 +165,7 @@ Kirigami.Page {
}
}
isDone: root.session.state === KeyVerificationSession.DONE
onDone: root.closeDialog()
onDone: root.Kirigami.PageStack.closeDialog()
}
}

View File

@@ -100,8 +100,7 @@ Kirigami.ApplicationWindow {
function onCurrentRoomChanged() {
if (RoomManager.currentRoom && root.pageStack.depth <= 1 && root.initialized && Kirigami.Settings.isMobile) {
let roomPage = pageStack.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
roomPage.forceActiveFocus();
let roomPage = root.pageStack.layers.push(Qt.createComponent('org.kde.neochat', 'RoomPage'));
roomPage.backRequested.connect(event => {
RoomManager.clearCurrentRoom();
});

View File

@@ -1,22 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import org.kde.kirigami as Kirigami
Kirigami.PromptDialog {
id: root
required property bool hasExistingMeeting
title: hasExistingMeeting ? i18nc("@title", "Join Meeting") : i18nc("@title", "Start Meeting")
subtitle: hasExistingMeeting ? i18nc("@info:label", "You are about to join a Jitsi meeting in your web browser.") : i18nc("@info:label", "You are about to start a new Jitsi meeting in your web browser.")
standardButtons: Kirigami.Dialog.Cancel
customFooterActions: Kirigami.Action {
icon.name: "camera-video-symbolic"
text: hasExistingMeeting ? i18nc("@action:button Join the Jitsi meeting", "Join") : i18nc("@action:button Start a new Jitsi meeting", "Start")
onTriggered: root.accept()
}
}

View File

@@ -23,45 +23,13 @@ Components.AlbumMaximizeComponent {
*/
required property NeoChatRoom currentRoom
readonly property string currentEventId: {
const idx = (content as ListView).currentIndex;
readonly property string currentEventId: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.EventIdRole)
if (idx === -1) {
return ""
}
readonly property var currentAuthor: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.AuthorRole)
return model.data(model.index(idx, 0), TimelineMessageModel.EventIdRole)
}
readonly property var currentTime: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.TimeRole)
readonly property var currentAuthor: {
const idx = (content as ListView).currentIndex;
if (idx === -1) {
return {}
}
return model.data(model.index(idx, 0), TimelineMessageModel.AuthorRole)
}
readonly property var currentTime: {
const idx = (content as ListView).currentIndex;
if (idx === -1) {
return {}
}
model.data(model.index(idx, 0), TimelineMessageModel.TimeRole)
}
readonly property var currentProgressInfo: {
const idx = (content as ListView).currentIndex;
if (idx === -1) {
return {}
}
model.data(model.index(idx, 0), TimelineMessageModel.ProgressInfoRole)
}
readonly property var currentProgressInfo: model.data(model.index((content as ListView).currentIndex, 0), TimelineMessageModel.ProgressInfoRole)
actions: [
ShareAction {

View File

@@ -59,59 +59,11 @@ Kirigami.Page {
*/
property MediaMessageFilterModel mediaMessageFilterModel: RoomManager.mediaMessageFilterModel
/**
* @brief The WidgetModel to use.
*
* This model has the list of widgets available in the current room.
*
* @note For loading a room in a different window, override this with a new
* WidgetModel.
*
* @sa WidgetModel
*/
property WidgetModel widgetModel: RoomManager.widgetModel
title: root.currentRoom ? root.currentRoom.displayName : ""
focus: true
padding: 0
actions: [
Kirigami.Action {
id: jitsiMeetingAction
readonly property bool hasExistingMeeting: root.widgetModel.jitsiIndex >= 0
readonly property bool canStartNewMeeting: root.currentRoom.canSendState("im.vector.modular.widgets")
tooltip: {
if (hasExistingMeeting) {
return i18nc("@action:button", "Join Jitsi meeting…");
}
return canStartNewMeeting ? i18nc("@action:button", "Start Jitsi meeting…") : i18nc("@action:button", "You do not have permissions to start Jitsi meetings")
}
icon {
name: "camera-video-symbolic"
color: hasExistingMeeting ? Kirigami.Theme.highlightColor : "transparent"
}
enabled: hasExistingMeeting || canStartNewMeeting
visible: root.currentRoom && !root.currentRoom.isSpace
onTriggered: {
const dialog = Qt.createComponent("org.kde.neochat", "MeetingDialog").createObject(QQC2.Overlay.overlay, { hasExistingMeeting });
dialog.onAccepted.connect(doAction);
dialog.open();
}
function doAction(): void {
let url;
if (!hasExistingMeeting) {
url = root.widgetModel.addJitsiConference();
} else {
let idx = root.widgetModel.index(root.widgetModel.jitsiIndex, 0);
url = root.widgetModel.data(idx, WidgetModel.UrlRole);
}
Qt.openUrlExternally(url);
}
},
Kirigami.Action {
visible: Kirigami.Settings.isMobile || !(root.Kirigami.PageStack.pageStack as Kirigami.PageRow).wideMode
icon.name: "view-right-new"
@@ -119,18 +71,6 @@ Kirigami.Page {
}
]
Kirigami.Action {
enabled: root.currentRoom && !root.currentRoom.isSpace
shortcut: "Ctrl+F"
onTriggered: {
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
room: root.currentRoom
}, {
title: i18nc("@action:title", "Search")
});
}
}
KeyNavigation.left: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).get(0)
onCurrentRoomChanged: {
@@ -227,8 +167,6 @@ Kirigami.Page {
// Used to keep track of messages so we can hide the right one at the right time
property string messageId
Layout.fillWidth: true
showCloseButton: true
visible: false
position: Kirigami.InlineMessage.Position.Header
@@ -255,6 +193,7 @@ Kirigami.Page {
id: timelineView
messageFilterModel: root.messageFilterModel
compactLayout: NeoChatConfig.compactLayout
fileDropEnabled: !Controller.isFlatpak
markReadCondition: NeoChatConfig.markReadCondition
}
}
@@ -287,6 +226,12 @@ Kirigami.Page {
}
}
background: Rectangle {
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
color: NeoChatConfig.compactLayout ? Kirigami.Theme.backgroundColor : "transparent"
}
footer: Loader {
id: chatBarLoader
height: active ? (item as ChatBar).implicitHeight : 0

View File

@@ -5,14 +5,13 @@ import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import io.github.quotient_im.libquotient
import org.kde.neochat
FormCard.FormCardPage {
id: root
property bool processing: false
title: i18nc("@title:window", "Load your encrypted messages")
topPadding: Kirigami.Units.gridUnit
@@ -27,42 +26,75 @@ FormCard.FormCardPage {
position: Kirigami.InlineMessage.Position.Header
}
Connections {
target: Controller.activeConnection
function onKeyBackupError(): void {
securityKeyField.clear()
root.processing = false
banner.text = i18nc("@info:status", "The security key or backup passphrase was not correct.")
banner.visible = true
}
property SSSSHandler ssssHandler: SSSSHandler {
id: ssssHandler
function onKeyBackupUnlocked(): void {
root.processing = false
property bool processing: false
connection: Controller.activeConnection
onKeyBackupUnlocked: {
ssssHandler.processing = false
banner.text = i18nc("@info:status", "Encryption keys restored.")
banner.type = Kirigami.MessageType.Positive
banner.visible = true
}
onError: error => {
if (error !== SSSSHandler.WrongKeyError) {
banner.text = error
banner.visible = true
return;
}
passwordField.clear()
ssssHandler.processing = false
banner.text = i18nc("@info:status", "The security phrase was not correct.")
banner.visible = true
}
}
FormCard.FormHeader {
title: i18nc("@title", "Unlock using Security Key or Backup Passphrase")
title: i18nc("@title", "Unlock using Passphrase")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
description: i18nc("@info", "If you have a security key or backup passphrase for this account, enter it below or upload it as a file.")
description: i18nc("@info", "If you have a backup passphrase for this account, enter it below.")
}
FormCard.FormTextFieldDelegate {
id: passwordField
label: i18nc("@label:textbox", "Backup Password:")
echoMode: TextInput.Password
}
FormCard.FormButtonDelegate {
id: unlockButton
text: i18nc("@action:button", "Unlock")
icon.name: "unlock"
enabled: passwordField.text.length > 0 && !ssssHandler.processing
onClicked: {
ssssHandler.processing = true
banner.visible = false
ssssHandler.unlockSSSSWithPassphrase(passwordField.text)
}
}
}
FormCard.FormHeader {
title: i18nc("@title", "Unlock using Security Key")
}
FormCard.FormCard {
FormCard.FormTextDelegate {
description: i18nc("@info", "If you have a security key for this account, enter it below or upload it as a file.")
}
FormCard.FormTextFieldDelegate {
id: securityKeyField
label: i18nc("@label:textbox", "Security Key or Backup Passphrase:")
label: i18nc("@label:textbox", "Security Key:")
echoMode: TextInput.Password
}
FormCard.FormButtonDelegate {
id: uploadSecurityKeyButton
text: i18nc("@action:button", "Upload from File")
icon.name: "cloud-upload"
enabled: !root.processing
enabled: !ssssHandler.processing
onClicked: {
root.processing = true
ssssHandler.processing = true
openFileDialog.open()
}
}
@@ -70,10 +102,10 @@ FormCard.FormCardPage {
id: unlockSecurityKeyButton
text: i18nc("@action:button", "Unlock")
icon.name: "unlock"
enabled: securityKeyField.text.length > 0 && !root.processing
enabled: securityKeyField.text.length > 0 && !ssssHandler.processing
onClicked: {
root.processing = true
Controller.activeConnection.unlockSSSS(securityKeyField.text)
ssssHandler.processing = true
ssssHandler.unlockSSSSFromSecurityKey(securityKeyField.text)
}
}
}
@@ -89,10 +121,10 @@ FormCard.FormCardPage {
id: unlockCrossSigningButton
icon.name: "emblem-shared-symbolic"
text: i18nc("@action:button", "Request from other Devices")
enabled: !root.processing
enabled: !ssssHandler.processing
onClicked: {
root.processing = true
Controller.activeConnection.unlockSSSS("")
ssssHandler.processing = true
ssssHandler.unlockSSSSFromCrossSigning()
}
}
}

View File

@@ -293,25 +293,5 @@ Kirigami.Dialog {
icon.name: "username-copy"
onClicked: Clipboard.saveText("https://matrix.to/#/" + root.user.id)
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report…")
icon.name: "dialog-warning-symbolic"
visible: root.connection.supportsMatrixSpecVersion("v1.13")
onClicked: {
let dialog = ((QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Report User"),
placeholder: i18nc("@info:placeholder", "Reason for reporting this user"),
icon: "dialog-warning-symbolic",
actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report")
}, {
title: i18nc("@title", "Report User"),
width: Kirigami.Units.gridUnit * 25
}) as ReasonDialog;
dialog.accepted.connect(reason => {
root.connection.reportUser(root.user.id, reason);
});
}
}
}
}

View File

@@ -30,7 +30,7 @@ SearchPage {
*/
required property NeoChatConnection connection
title: i18nc("@action:title", "Find User")
title: i18nc("@action:title", "Find Your Friends")
Component.onCompleted: focusSearch()
@@ -81,7 +81,7 @@ SearchPage {
}
QQC2.Label {
visible: userDelegate.directChatExists
text: i18nc("@info", "Direct Messages")
text: i18nc("@info", "Friends")
textFormat: Text.PlainText
color: Kirigami.Theme.positiveTextColor
}

View File

@@ -5,25 +5,14 @@ import QtQuick
import QtQml
import org.kde.neochat
import io.github.quotient_im.libquotient
VerificationMessage {
id: root
required property int reason
icon: {
switch (root.reason) {
case KeyVerificationSession.TIMEOUT:
case KeyVerificationSession.REMOTE_TIMEOUT:
case KeyVerificationSession.USER:
case KeyVerificationSession.REMOTE_USER:
case KeyVerificationSession.SESSION_ACCEPTED:
case KeyVerificationSession.REMOTE_SESSION_ACCEPTED:
return "dialog-information";
default:
return "security-low";
}
}
icon: "security-low"
text: {
switch (root.reason) {
case KeyVerificationSession.NONE:

View File

@@ -9,6 +9,8 @@
#include "controller.h"
#include "eventhandler.h"
#include "models/actionsmodel.h"
#include "models/messagefiltermodel.h"
#include "models/sortfilterroomtreemodel.h"
#include "neochatconfig.h"
#include "neochatconnection.h"
#include "neochatroom.h"
@@ -41,7 +43,6 @@ RoomManager::RoomManager(QObject *parent)
, m_messageFilterModel(new MessageFilterModel(this, m_timelineModel))
, m_mediaMessageFilterModel(new MediaMessageFilterModel(this, m_messageFilterModel))
, m_userListModel(new UserListModel(this))
, m_widgetModel(new WidgetModel(this))
{
#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(UBUNTU_TOUCH)
m_isMobile = true;
@@ -54,7 +55,6 @@ RoomManager::RoomManager(QObject *parent)
#endif
connect(this, &RoomManager::currentRoomChanged, this, [this]() {
m_widgetModel->setRoom(m_currentRoom);
m_userListModel->setRoom(m_currentRoom);
m_timelineModel->setRoom(m_currentRoom);
m_sortFilterRoomTreeModel->setCurrentRoom(m_currentRoom);
@@ -197,11 +197,6 @@ void RoomManager::activateUserModel()
m_userListModel->activate();
}
WidgetModel *RoomManager::widgetModel() const
{
return m_widgetModel;
}
void RoomManager::resolveResource(const QString &idOrUri, const QString &action)
{
resolveResource(Uri{idOrUri}, action);
@@ -519,14 +514,7 @@ void RoomManager::setCurrentSpace(const QString &spaceId, bool setRoom)
// This need to happen before the signal so TreeView.expandRecursively() can work nicely.
m_sortFilterRoomTreeModel->setActiveSpaceId(m_currentSpaceId);
if (m_currentSpaceId == u"DM") {
m_sortFilterRoomTreeModel->setMode(SortFilterRoomTreeModel::DirectChats);
} else if (m_currentSpaceId.isEmpty()) {
m_sortFilterRoomTreeModel->setMode(SortFilterRoomTreeModel::Rooms);
} else {
m_sortFilterRoomTreeModel->setMode(SortFilterRoomTreeModel::All);
}
m_sortFilterRoomTreeModel->setMode(m_currentSpaceId == u"DM"_s ? SortFilterRoomTreeModel::DirectChats : SortFilterRoomTreeModel::Rooms);
Q_EMIT currentSpaceChanged();
if (m_connection) {
@@ -590,8 +578,7 @@ void RoomManager::setCurrentRoom(const QString &roomId)
return;
}
if (m_currentRoom->isDirectChat()) {
const auto roomsInSpace = SpaceHierarchyCache::instance().getRoomListForSpace(m_currentSpaceId, false);
if (!roomsInSpace.contains(m_currentRoom->id()) && m_currentSpaceId != "DM"_L1) {
if (m_currentSpaceId != "DM"_L1) {
setCurrentSpace("DM"_L1, false);
}
return;

View File

@@ -22,7 +22,6 @@
#include "models/sortfilterspacelistmodel.h"
#include "models/timelinemodel.h"
#include "models/userlistmodel.h"
#include "models/widgetmodel.h"
#include "neochatroommember.h"
class NeoChatRoom;
@@ -130,14 +129,6 @@ class RoomManager : public QObject, public UriResolverBase
*/
Q_PROPERTY(UserListModel *userListModel READ userListModel CONSTANT)
/**
* @brief The WidgetModel that should be used for room widget visualisation.
*
* @note Available here so that the room page and drawer both have access to the
* same model.
*/
Q_PROPERTY(WidgetModel *widgetModel READ widgetModel CONSTANT)
/**
* @brief Whether a room is currently open in NeoChat.
*
@@ -169,8 +160,6 @@ public:
UserListModel *userListModel() const;
Q_INVOKABLE void activateUserModel();
WidgetModel *widgetModel() const;
/**
* @brief Resolve the given resource.
*
@@ -367,7 +356,6 @@ private:
MediaMessageFilterModel *m_mediaMessageFilterModel;
UserListModel *m_userListModel;
WidgetModel *m_widgetModel;
QPointer<NeoChatConnection> m_connection;

View File

@@ -8,6 +8,8 @@
#include <KWindowSystem>
#include "controller.h"
#include "models/roomlistmodel.h"
#include "models/sortfilterroomlistmodel.h"
#include "roommanager.h"
#include "windowcontroller.h"

View File

@@ -263,7 +263,6 @@ QQC2.Control {
wrapMode: TextEdit.Wrap
// This has to stay PlainText or else formatting starts breaking in strange ways
textFormat: TextEdit.PlainText
font.pointSize: Kirigami.Theme.defaultFont.pointSize * NeoChatConfig.fontScale
Accessible.description: placeholderText
@@ -286,8 +285,6 @@ QQC2.Control {
quickFormatBar.selectionStart = selectionStart;
quickFormatBar.selectionEnd = selectionEnd;
quickFormatBar.open();
} else if (quickFormatBar.visible) {
quickFormatBar.close();
}
}
@@ -378,9 +375,7 @@ QQC2.Control {
id: actionDelegate
required property BusyAction modelData
icon.name: modelData.isBusy ? "" : (modelData.icon.name.length > 0 ? modelData.icon.name : modelData.icon.source)
onClicked: if (!pieProgress.visible) {
modelData.trigger()
}
onClicked: modelData.trigger()
padding: Kirigami.Units.smallSpacing
@@ -388,9 +383,7 @@ QQC2.Control {
QQC2.ToolTip.text: modelData.tooltip
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
PieProgressBar {
id: pieProgress
anchors.fill: parent
contentItem: PieProgressBar {
visible: actionDelegate.modelData.isBusy
progress: root.currentRoom.fileUploadingProgress
}

View File

@@ -4,7 +4,6 @@
import QtQuick
import QtQuick.Controls as QQC2
import org.kde.kirigami as Kirigami
import org.kde.neochat
QQC2.ItemDelegate {
id: root
@@ -30,7 +29,6 @@ QQC2.ItemDelegate {
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
font.family: "emoji"
font.pointSize: Kirigami.Theme.defaultFont.pointSize * NeoChatConfig.fontScale
Kirigami.Icon {
width: Kirigami.Units.gridUnit * 0.5

View File

@@ -17,7 +17,6 @@ Kirigami.ScrollablePage {
property NeoChatRoom room
required property NeoChatConnection connection
property alias currentTabIndex: tabBar.currentIndex
title: i18nc("@title", "Developer Tools")

View File

@@ -21,6 +21,12 @@ FormCard.FormCard {
onToggled: NeoChatConfig.threads = checked
}
FormCard.FormCheckDelegate {
text: i18nc("@option:check Enable the matrix 'secret backup' feature", "Secret Backup")
checked: NeoChatConfig.secretBackup
onToggled: NeoChatConfig.secretBackup = checked
}
FormCard.FormCheckDelegate {
text: i18nc("@option:check Enable the matrix feature to add a phone number as a third party ID", "Add phone numbers as 3PIDs")
checked: NeoChatConfig.phone3PId

View File

@@ -33,8 +33,6 @@ target_sources(LibNeoChat PRIVATE
events/imagepackevent.cpp
events/pollevent.cpp
jobs/neochatgetcommonroomsjob.cpp
jobs/neochatreportroomjob.cpp
jobs/neochatreportuserjob.cpp
models/actionsmodel.cpp
models/completionmodel.cpp
models/completionproxymodel.cpp
@@ -47,8 +45,6 @@ target_sources(LibNeoChat PRIVATE
models/stickermodel.cpp
models/userfiltermodel.cpp
models/userlistmodel.cpp
models/widgetmodel.cpp
models/widgetmodel.h
)
if (TARGET KF6::KIOWidgets)

View File

@@ -3,10 +3,6 @@
#include "chatbarcache.h"
#include <QMimeData>
#include <KUrlMimeData>
#include <Quotient/roommember.h>
#include "eventhandler.h"
@@ -299,17 +295,4 @@ void ChatBarCache::clearCache()
clearRelations();
}
void ChatBarCache::drop(QList<QUrl> u, const QString &transferPortal)
{
QMimeData mimeData;
mimeData.setUrls(u);
if (!transferPortal.isEmpty()) {
mimeData.setData(u"application/vnd.portal.filetransfer"_s, transferPortal.toLatin1());
}
auto urls = KUrlMimeData::urlsFromMimeData(&mimeData);
if (urls.size() > 0) {
setAttachmentPath(urls[0].toString());
}
}
#include "moc_chatbarcache.cpp"

View File

@@ -198,8 +198,6 @@ public:
*/
Q_INVOKABLE void postMessage();
Q_INVOKABLE void drop(QList<QUrl> urls, const QString &transferPortal);
Q_SIGNALS:
void textChanged();
void relationIdChanged(const QString &oldEventId, const QString &newEventId);

View File

@@ -30,7 +30,7 @@ public:
ServerNotice, /**< Official messages from the server. */
Deprioritized, /**< The room is set as low priority. */
Space, /**< The room is a space. */
AddDirect, /**< So we can show the add direct message delegate. */
AddDirect, /**< So we can show the add friend delegate. */
TypesCount, /**< Number of different types (this should always be last). */
};
Q_ENUM(Types);
@@ -66,7 +66,7 @@ public:
case NeoChatRoomType::Favorite:
return i18n("Favorite");
case NeoChatRoomType::Direct:
return i18n("Direct Messages");
return i18n("Friends");
case NeoChatRoomType::Normal:
return i18n("Normal");
case NeoChatRoomType::Deprioritized:

View File

@@ -89,7 +89,7 @@ public:
case Parameter::MostHighlights:
return i18nc("@info", "Rooms with the most highlighted messages are higher");
case Parameter::LastActive:
return i18nc("@info", "Rooms with newer events are higher");
return i18nc("@info", "Rooms with the newer messages are higher");
default:
return {};
}

View File

@@ -10,6 +10,7 @@
#include <Quotient/events/encryptionevent.h>
#include <Quotient/events/event.h>
#include <Quotient/events/eventcontent.h>
#include <Quotient/events/reactionevent.h>
#include <Quotient/events/redactionevent.h>
#include <Quotient/events/roomavatarevent.h>
@@ -195,25 +196,20 @@ bool EventHandler::isHidden(const NeoChatRoom *room, const Quotient::RoomEvent *
return false;
}
Qt::TextFormat EventHandler::messageBodyInputFormat(const Quotient::RoomEvent &event)
Qt::TextFormat EventHandler::messageBodyInputFormat(const Quotient::RoomMessageEvent &event)
{
if (event.isRedacted() && !event.isStateEvent()) {
return Qt::RichText;
}
auto msgEvent = eventCast<const Quotient::RoomMessageEvent>(&event);
if (!msgEvent) {
return Qt::PlainText;
}
if (msgEvent->mimeType().name() == "text/plain"_L1) {
if (event.mimeType().name() == "text/plain"_L1) {
return Qt::PlainText;
} else {
return Qt::RichText;
}
}
QString EventHandler::rawMessageBody(const RoomEvent &event)
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
{
if (event.isRedacted() && !event.isStateEvent()) {
auto reason = event.redactedBecause()->reason();
@@ -222,26 +218,21 @@ QString EventHandler::rawMessageBody(const RoomEvent &event)
QString body;
auto msgEvent = eventCast<const Quotient::RoomMessageEvent>(&event);
if (!msgEvent) {
return body;
}
if (msgEvent->has<EventContent::FileContent>()) {
if (event.has<EventContent::FileContent>()) {
// if filename is given or body is equal to filename,
// then body is a caption
QString filename = msgEvent->get<EventContent::FileContent>()->originalName;
QString body = msgEvent->plainBody();
QString filename = event.get<EventContent::FileContent>()->originalName;
QString body = event.plainBody();
if (filename.isEmpty() || filename == body) {
return QString();
}
return body;
}
if (msgEvent->has<EventContent::TextContent>() && msgEvent->content()) {
body = msgEvent->get<EventContent::TextContent>()->body;
if (event.has<EventContent::TextContent>() && event.content()) {
body = event.get<EventContent::TextContent>()->body;
} else {
body = msgEvent->plainBody();
body = event.plainBody();
}
return body;
}
@@ -389,9 +380,9 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
return i18n("left the room");
}
if (const auto &reason = e.contentJson()["reason"_L1].toString().toHtmlEscaped(); !reason.isEmpty()) {
return i18n("has removed %1 from the room: %2", subjectName, reason);
return i18n("has put %1 out of the room: %2", subjectName, reason);
}
return i18n("has removed %1 from the room", subjectName);
return i18n("has put %1 out of the room", subjectName);
case Membership::Ban:
if (e.senderId() != e.userId()) {
if (e.reason().isEmpty()) {
@@ -460,9 +451,6 @@ QString EventHandler::getBody(const NeoChatRoom *room, const Quotient::RoomEvent
return i18nc("[User] joined a [voice/video] call", "joined a call");
}
}
if (e.matrixType() == "io.element.integrations.installations"_L1) {
return i18nc("[User] configured an extension", "configured an extension");
}
return e.stateKey().isEmpty() ? i18n("updated %1 state", e.matrixType())
: i18n("updated %1 state for %2", e.matrixType(), prettyPrint ? e.stateKey().toHtmlEscaped() : e.stateKey());
},
@@ -676,9 +664,6 @@ QString EventHandler::genericBody(const NeoChatRoom *room, const Quotient::RoomE
return i18nc("[User] joined a [voice/video] call", "%1 joined a call", senderString);
}
}
if (e.matrixType() == "io.element.integrations.installations"_L1) {
return i18nc("[User] configured an extension", "%1 configured an extension", senderString);
}
return i18n("%1 updated the state", senderString);
},
[senderString](const PollStartEvent &) {

View File

@@ -122,15 +122,15 @@ public:
* I.e. if the message has only a body the format will be Qt::PlainText, if it
* has a formatted body it will be Qt::RichText.
*/
static Qt::TextFormat messageBodyInputFormat(const Quotient::RoomEvent &event);
static Qt::TextFormat messageBodyInputFormat(const Quotient::RoomMessageEvent &event);
/**
* @brief Output a string for the message content without any formatting.
* @brief Output a string for the room message content without any formatting.
*
* This is the content of the formatted_body key if present or the body key if
* not.
*/
static QString rawMessageBody(const Quotient::RoomEvent &event);
static QString rawMessageBody(const Quotient::RoomMessageEvent &event);
/**
* @brief Output a string for the message content ready for display in a rich text field.

View File

@@ -2,6 +2,7 @@
// SPDX-License-Identifier: LGPL-2.0-or-later
#include "pollevent.h"
#include <Quotient/converters.h>
using namespace Quotient;

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "neochatreportroomjob.h"
using namespace Quotient;
NeochatReportRoomJob::NeochatReportRoomJob(const QString &userId, const QString &reason)
: BaseJob(HttpVerb::Post, u"ReportRoomJob"_s, makePath(" /_matrix/client/v3/", userId, "/report"))
{
QJsonObject _dataJson;
addParam<IfNotEmpty>(_dataJson, "reason"_L1, reason);
setRequestData({_dataJson});
}

View File

@@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Quotient/jobs/basejob.h>
// TODO: Remove once libQuotient updates to Matrix API v1.14
class NeochatReportRoomJob : public Quotient::BaseJob
{
public:
explicit NeochatReportRoomJob(const QString &roomId, const QString &reason);
};

View File

@@ -1,14 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#include "neochatreportuserjob.h"
using namespace Quotient;
NeochatReportUserJob::NeochatReportUserJob(const QString &userId, const QString &reason)
: BaseJob(HttpVerb::Post, u"ReportUserJob"_s, makePath("/_matrix/client/v3/users/", userId, "/report"))
{
QJsonObject _dataJson;
addParam<IfNotEmpty>(_dataJson, "reason"_L1, reason);
setRequestData({_dataJson});
}

View File

@@ -1,13 +0,0 @@
// SPDX-FileCopyrightText: 2025 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <Quotient/jobs/basejob.h>
// TODO: Remove once libQuotient updates to Matrix API v1.14
class NeochatReportUserJob : public Quotient::BaseJob
{
public:
explicit NeochatReportUserJob(const QString &userId, const QString &reason);
};

View File

@@ -146,13 +146,9 @@ bool UserListModel::event(QEvent *event)
void UserListModel::memberJoined(const Quotient::RoomMember &member)
{
if (m_members.contains(member.id())) {
return;
}
const int row = m_members.size();
beginInsertRows(QModelIndex(), row, row);
m_members.append(member.id());
auto pos = findUserPos(member);
beginInsertRows(QModelIndex(), pos, pos);
m_members.insert(pos, member.id());
endInsertRows();
}
@@ -172,6 +168,8 @@ void UserListModel::refreshMember(const Quotient::RoomMember &member, const QLis
{
auto pos = findUserPos(member);
if (pos != m_members.size()) {
// The update will have changed the state event so we need to insert the updated member object.
m_members.insert(pos, member.id());
Q_EMIT dataChanged(index(pos), index(pos), roles);
} else {
qWarning() << "Trying to access a room member not in the user list";

View File

@@ -1,356 +0,0 @@
// SPDX-FileCopyrightText: 2025 Arno Rehn <arno@arnorehn.de>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "widgetmodel.h"
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/user.h>
#include <widgetevent.h>
using namespace Quotient;
namespace
{
// This URL will take us to a jitsi room that is configured on a best effort basis.
// Critically, it appears that we cannot pass avatars via the "fragment API".
// The important stuff is working though: conference id, user name, subject.
//
// The alternative would be to rely on a Jitsi wrapper like https://app.element.io/jitsi.html.
// This one parses the fragment and then uses the the actual Jitsi IFrame API to set
// up the Jitsi meeting. Nice, but ties us to that external wrapper.
//
// Or we ship our own wrapper as a local HTML file and open that one?
constexpr char JitsiMeetUrlTemplate[] =
"https://$domain/$conferenceId"
"#jitsi_meet_external_api_id=0"
"&config.subject=\"$roomName\""
"&config.startAudioOnly=$isAudioOnly"
"&config.startWithAudioMuted=$startWithAudioMuted"
"&config.startWithVideoMuted=$startWithVideoMuted"
"&isVideoChannel=$isVideoChannel"
"&userInfo.displayName=\"$matrix_display_name\"";
// Same thing with etherpad, as long as it's on scalar.vector.im
constexpr char EtherpadScalarVectorImUrl[] = "https://scalar.vector.im/api/widgets/etherpad.html";
constexpr char EtherpadUrlTemplate[] =
"https://etherpad.integrations.element.io/p/"
"$padName"
"?showControls=true"
"&showChat=false"
"&chatAndUsers=false"
"&alwaysShowChat=false"
"&showLineNumbers=true"
"&useMonospaceFont=false"
"&userName=$matrix_user_id";
QUrl avatarUrl(NeoChatRoom *room)
{
const auto avatarUrl = room->member(room->connection()->userId()).avatarUrl();
return room->connection()->getUrlForApi<DownloadFileJob>(avatarUrl);
}
// Many widgets require proper interfacing with integration managers, but this needs authentication
// with an integration manager: https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/1961-integrations-auth.md
// Also requires accepting the terms: https://github.com/matrix-org/matrix-spec-proposals/blob/main/proposals/2140-terms-of-service-2.md
// Example:
// curl -X POST "https://scalar.vector.im/_matrix/integrations/v1/register" \
// -H "Content-Type: application/json" \
// --data '<OpenIdCredentials>'
// Then append the resulting json key-value pairs as query items to the widget url.
QUrl buildWidgetUrl(QByteArray templateString, NeoChatRoom *room, const QJsonObject &data)
{
constexpr auto enc = [](const auto &s) {
return QUrl::toPercentEncoding(s);
};
templateString.replace("$matrix_user_id"_L1, enc(room->connection()->userId()))
.replace("$matrix_room_id"_L1, enc(room->id()))
.replace("$matrix_display_name"_L1, enc(room->member(room->connection()->userId()).displayName()))
.replace("$matrix_avatar_url"_L1, avatarUrl(room).toEncoded());
for (auto it = data.begin(); it != data.end(); ++it) {
const QByteArray key = '$' + it.key().toUtf8();
templateString.replace(key, enc(it.value().toString()));
}
return QUrl::fromEncoded(templateString);
}
}
class WidgetModelPrivate
{
Q_DISABLE_COPY(WidgetModelPrivate);
public:
WidgetModelPrivate(WidgetModel *q)
: q_ptr(q)
{
}
Q_DECLARE_PUBLIC(WidgetModel)
WidgetModel *const q_ptr;
// clang-format off
struct NoModelResetTag_t {};
static constexpr NoModelResetTag_t NoModelReset {};
// clang-format on
void reload();
void reload(NoModelResetTag_t);
void updateWidgets(Quotient::RoomEventsRange events);
void handleEvent(const Quotient::RoomEvent *event);
void handlePendingEvent(const Quotient::RoomEvent *event);
void buildJitsiIndex();
NeoChatRoom *room = nullptr;
QMap<QString, const WidgetEvent *> state;
int jitsiIndex = -1;
};
WidgetModel::WidgetModel(QObject *parent)
: QAbstractListModel{parent}
, d_ptr(std::make_unique<WidgetModelPrivate>(this))
{
}
WidgetModel::~WidgetModel()
{
}
QHash<int, QByteArray> WidgetModel::roleNames() const
{
return {
{TextRole, "text"},
{UrlRole, "url"},
{TypeRole, "type"},
};
}
QVariant WidgetModel::data(const QModelIndex &index, int role) const
{
Q_D(const WidgetModel);
const int row = index.row();
if (row < 0 || row >= d->state.count()) {
return {};
}
const auto *ev = std::next(d->state.begin(), row).value();
const auto type = ev->contentPart<QString>("type"_L1);
const auto url = ev->contentPart<QString>("url"_L1).toUtf8();
switch (role) {
case TextRole:
return ev->contentPart<QString>("name"_L1);
case TypeRole:
return type;
case UrlRole: {
QByteArray urlTemplate = url;
if (type == "jitsi"_L1) {
// Jitsi is special-cased even in Element. The URL that we get from the
// widget is just "https://scalar.vector.im/api/widgets/jitsi.html" which
// is not sufficient.
urlTemplate = JitsiMeetUrlTemplate;
} else if (type == "m.etherpad"_L1 && url.startsWith(EtherpadScalarVectorImUrl)) {
// Etherpad is not special-cased, but the scalar.vector.im wrapper requires authentication
// with the integration manager. We can just resolve this to the actual etherpad instance
// to skip integration manager auth.
urlTemplate = EtherpadUrlTemplate;
}
return buildWidgetUrl(urlTemplate, room(), ev->contentPart<QJsonObject>("data"_L1));
}
}
return {};
}
int WidgetModel::rowCount(const QModelIndex &parent) const
{
Q_D(const WidgetModel);
Q_UNUSED(parent);
return d->state.count();
}
void WidgetModelPrivate::reload(NoModelResetTag_t)
{
state.clear();
if (room) {
const auto events = room->currentState().eventsOfType(Quotient::WidgetEvent::MetaType.matrixId);
for (auto ev : events) {
handleEvent(ev);
}
}
}
void WidgetModelPrivate::reload()
{
Q_Q(WidgetModel);
q->beginResetModel();
reload(NoModelReset);
buildJitsiIndex();
q->endResetModel();
}
void WidgetModelPrivate::updateWidgets(Quotient::RoomEventsRange events)
{
using namespace std::ranges;
constexpr auto isWidgetEvent = [](auto &&ev) {
return ev->template is<WidgetEvent>();
};
if (any_of(events, isWidgetEvent)) {
reload();
}
}
void WidgetModelPrivate::handleEvent(const Quotient::RoomEvent *event)
{
const WidgetEvent *widgetEvent = eventCast<const WidgetEvent>(event);
if (!widgetEvent) {
return;
}
auto it = state.find(widgetEvent->stateKey());
if (it != state.end() && widgetEvent->replacedState() == it.value()->id()) {
// we alrady have this and it should be...
if (widgetEvent->contentJson().isEmpty()) {
// ... removed because replacement is empty
state.erase(it);
} else {
// replaced
it.value() = widgetEvent;
}
} else if (!widgetEvent->contentJson().isEmpty()) {
// insert if not empty
it = state.insert(widgetEvent->stateKey(), widgetEvent);
}
}
void WidgetModelPrivate::handlePendingEvent(const RoomEvent *event)
{
Q_Q(WidgetModel);
if (!event->is<WidgetEvent>()) {
return;
}
q->beginResetModel();
reload(NoModelReset);
handleEvent(event);
buildJitsiIndex();
q->endResetModel();
}
void WidgetModelPrivate::buildJitsiIndex()
{
Q_Q(WidgetModel);
const auto newIndex = [&] -> int {
auto it = std::find_if(state.cbegin(), state.cend(), [](const WidgetEvent *e) {
return e->contentPart<QString>("type"_L1) == "jitsi"_L1;
});
if (it == state.cend()) {
return -1;
}
return std::distance(state.cbegin(), it);
}();
if (newIndex != jitsiIndex) {
jitsiIndex = newIndex;
Q_EMIT q->jitsiIndexChanged();
}
}
NeoChatRoom *WidgetModel::room() const
{
Q_D(const WidgetModel);
return d->room;
}
void WidgetModel::setRoom(NeoChatRoom *newRoom)
{
Q_D(WidgetModel);
if (d->room == newRoom) {
return;
}
if (d->room) {
disconnect(d->room, &NeoChatRoom::baseStateLoaded, this, nullptr);
disconnect(d->room, &NeoChatRoom::aboutToAddNewMessages, this, nullptr);
disconnect(d->room, &NeoChatRoom::pendingEventAboutToAdd, this, nullptr);
}
d->room = newRoom;
if (d->room) {
connect(d->room, &NeoChatRoom::baseStateLoaded, this, [d] {
d->reload();
});
connect(d->room, &NeoChatRoom::aboutToAddNewMessages, this, [d](Quotient::RoomEventsRange events) {
d->updateWidgets(events);
});
connect(d->room, &NeoChatRoom::pendingEventAboutToAdd, this, [d](Quotient::RoomEvent *event) {
d->handlePendingEvent(event);
});
}
d->reload();
Q_EMIT roomChanged();
}
int WidgetModel::jitsiIndex() const
{
Q_D(const WidgetModel);
return d->jitsiIndex;
}
QUrl WidgetModel::addJitsiConference()
{
// URL is not spec-compliant, but this is what Element does as well.
const auto conferenceId = QUuid::createUuid().toString(QUuid::WithoutBraces);
// clang-format off
const QJsonObject content{
{"name"_L1, "Jitsi Meet"_L1},
{"type"_L1, "jitsi"_L1},
{"url"_L1, "https://scalar.vector.im/api/widgets/jitsi.html"_L1},
{"data"_L1, QJsonObject{
{"conferenceId"_L1, conferenceId},
{"domain"_L1, "meet.element.io"_L1}, // TODO: make domain configurable
{"isAudioOnly"_L1, false},
{"roomName"_L1, room()->displayName()},
}},
};
// clang-format on
// Re-use conferenceId as state_key
room()->setState(WidgetEvent::MetaType.matrixId, conferenceId, content);
return buildWidgetUrl(JitsiMeetUrlTemplate, room(), content["data"_L1].toObject());
}
bool WidgetModel::removeWidget(int index)
{
Q_D(const WidgetModel);
if (index < 0 || index >= rowCount()) {
return false;
}
const auto stateKey = std::next(d->state.begin(), index).value()->stateKey();
room()->setState(WidgetEvent::MetaType.matrixId, stateKey, QJsonObject{});
return true;
}

View File

@@ -1,74 +0,0 @@
// SPDX-FileCopyrightText: 2025 Arno Rehn <arno@arnorehn.de>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#ifndef WIDGETMODEL_H
#define WIDGETMODEL_H
#include <QAbstractListModel>
#include <QQmlEngine>
#include "neochatroom.h"
class WidgetModelPrivate;
/**
* @class WidgetModel
*
* `WidgetModel` provides a list model of widgets for a given room.
*
* It also supports adding and removing widgets.
*/
class WidgetModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
/**
* @brief The room for which this model lists the widgets
*/
Q_PROPERTY(NeoChatRoom *room READ room WRITE setRoom NOTIFY roomChanged)
/**
* @brief The row index of the first Jitsi widget
*/
Q_PROPERTY(int jitsiIndex READ jitsiIndex NOTIFY jitsiIndexChanged)
public:
enum Roles {
TextRole = Qt::DisplayRole,
UrlRole = Qt::UserRole,
TypeRole,
};
Q_ENUM(Roles)
explicit WidgetModel(QObject *parent = nullptr);
~WidgetModel() override;
NeoChatRoom *room() const;
void setRoom(NeoChatRoom *newRoom);
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
[[nodiscard]] int rowCount(const QModelIndex &parent = {}) const override;
int jitsiIndex() const;
/**
* @brief Adds a new Jitsi widget
* @return The URL of the newly added Jitsi conference
*/
Q_INVOKABLE QUrl addJitsiConference();
/**
* @brief Removes the widget at @p index
* @return `true` on success, `false` otherwise
*/
Q_INVOKABLE bool removeWidget(int index);
Q_SIGNALS:
void roomChanged();
void jitsiIndexChanged();
private:
Q_DECLARE_PRIVATE(WidgetModel)
const std::unique_ptr<WidgetModelPrivate> d_ptr;
};
#endif // WIDGETMODEL_H

View File

@@ -6,10 +6,10 @@
#include <QImageReader>
#include <QJsonDocument>
#include "jobs/neochatreportuserjob.h"
#include "neochatroom.h"
#include "spacehierarchycache.h"
#include <Quotient/connection.h>
#include <Quotient/jobs/basejob.h>
#include <Quotient/quotient_common.h>
#include <qt6keychain/keychain.h>
@@ -20,7 +20,6 @@
#include <Quotient/csapi/profile.h>
#include <Quotient/csapi/registration.h>
#include <Quotient/csapi/versions.h>
#include <Quotient/e2ee/sssshandler.h>
#include <Quotient/jobs/downloadfilejob.h>
#include <Quotient/qt_connection_util.h>
#include <Quotient/room.h>
@@ -97,7 +96,6 @@ void NeoChatConnection::connectSignals()
Q_EMIT directChatsHaveHighlightNotificationsChanged();
});
}
Q_EMIT roomInvitesChanged();
connect(room, &Room::unreadStatsChanged, this, [this]() {
refreshBadgeNotificationCount();
Q_EMIT homeNotificationsChanged();
@@ -162,10 +160,6 @@ void NeoChatConnection::refreshBadgeNotificationCount()
for (const auto &r : allRooms()) {
if (const auto room = static_cast<NeoChatRoom *>(r)) {
count += room->contextAwareNotificationCount();
if (room->joinState() == JoinState::Invite) {
count++;
}
}
}
@@ -459,20 +453,15 @@ bool NeoChatConnection::homeHaveHighlightNotifications() const
return false;
}
qsizetype NeoChatConnection::directChatInvites() const
bool NeoChatConnection::directChatInvites() const
{
const auto inviteRooms = rooms(JoinState::Invite);
return std::ranges::count_if(inviteRooms, [](const auto room) {
return room->isDirectChat();
});
}
qsizetype NeoChatConnection::roomInvites() const
{
const auto inviteRooms = rooms(JoinState::Invite);
return std::ranges::count_if(inviteRooms, [](const auto room) {
return !room->isDirectChat();
});
auto inviteRooms = rooms(JoinState::Invite);
for (const auto inviteRoom : inviteRooms) {
if (inviteRoom->isDirectChat()) {
return true;
}
}
return false;
}
QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
@@ -576,36 +565,4 @@ bool NeoChatConnection::isVerifiedSession() const
return isVerifiedDevice(userId(), deviceId());
}
void NeoChatConnection::unlockSSSS(const QString &secret)
{
auto handler = new SSSSHandler();
handler->setConnection(this);
connect(handler, &SSSSHandler::error, this, [secret, handler, this]() {
disconnect(handler, &SSSSHandler::error, this, nullptr);
if (!secret.isEmpty()) {
connect(handler, &SSSSHandler::error, this, [handler, this]() {
Q_EMIT keyBackupError();
delete handler;
});
handler->unlockSSSSWithPassphrase(secret);
} else {
Q_EMIT keyBackupError();
}
});
connect(handler, &SSSSHandler::keyBackupUnlocked, this, [handler, this]() {
Q_EMIT keyBackupUnlocked();
connect(handler, &SSSSHandler::finished, handler, &SSSSHandler::deleteLater);
});
if (secret.isEmpty()) {
handler->unlockSSSSFromCrossSigning();
} else {
handler->unlockSSSSFromSecurityKey(secret);
}
}
void NeoChatConnection::reportUser(const QString &userId, const QString &reason)
{
callApi<NeochatReportUserJob>(userId, reason);
}
#include "moc_neochatconnection.cpp"

View File

@@ -66,14 +66,9 @@ class NeoChatConnection : public Quotient::Connection
Q_PROPERTY(bool homeHaveHighlightNotifications READ homeHaveHighlightNotifications NOTIFY homeHaveHighlightNotificationsChanged)
/**
* @brief The number of invites to 1-on-1 direct chats.
* @brief Whether there is at least one invite to a direct chat.
*/
Q_PROPERTY(qsizetype directChatInvites READ directChatInvites NOTIFY directChatInvitesChanged)
/**
* @brief The number of pending, normal room invites.
*/
Q_PROPERTY(qsizetype roomInvites READ roomInvites NOTIFY roomInvitesChanged)
Q_PROPERTY(bool directChatInvites READ directChatInvites NOTIFY directChatInvitesChanged)
/**
* @brief Whether the server supports querying a user's mutual rooms.
@@ -205,8 +200,7 @@ public:
*/
static void setKeywordPushRuleDefault(PushRuleAction::Action defaultAction);
qsizetype directChatInvites() const;
qsizetype roomInvites() const;
bool directChatInvites() const;
// note: this is intentionally a copied QString because
// the reference could be destroyed before the task is finished
@@ -222,13 +216,6 @@ public:
*/
bool isVerifiedSession() const;
Q_INVOKABLE void unlockSSSS(const QString &secret);
/**
* @brief Report a user.
*/
Q_INVOKABLE void reportUser(const QString &userId, const QString &reason);
Q_SIGNALS:
void globalUrlPreviewEnabledChanged();
void labelChanged();
@@ -238,7 +225,6 @@ Q_SIGNALS:
void homeNotificationsChanged();
void homeHaveHighlightNotificationsChanged();
void directChatInvitesChanged();
void roomInvitesChanged();
void passwordStatus(NeoChatConnection::PasswordStatus status);
void userConsentRequired(QUrl url);
void badgeNotificationCountChanged(int count);
@@ -266,9 +252,6 @@ Q_SIGNALS:
*/
void ownSessionVerified();
void keyBackupUnlocked();
void keyBackupError();
private:
static bool m_globalUrlPreviewDefault;
static PushRuleAction::Action m_defaultAction;

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