Compare commits

..

1 Commits

Author SHA1 Message Date
Tobias Fella
658551e64d Add opencv to flatpak
Dependency of KQuickImageEditor
2025-09-01 15:44:54 +02:00
253 changed files with 51483 additions and 98830 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,
@@ -194,6 +206,14 @@
{
"type": "dir",
"path": "."
},
{
"type": "patch",
"path": "patches/0001-Revert-Bump-KF6-dependency-version.patch"
},
{
"type": "patch",
"path": "patches/0001-Revert-Use-new-Kirigami-builtin-column-resize-handle.patch"
}
]
}

View File

@@ -10,11 +10,11 @@ Dependencies:
'frameworks/ki18n': '@latest-kf6'
'frameworks/kconfig': '@latest-kf6'
'frameworks/syntax-highlighting': '@latest-kf6'
'frameworks/kiconthemes': '@latest-kf6'
'frameworks/kitemmodels': '@latest-kf6'
'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'
@@ -29,6 +29,7 @@ Dependencies:
'frameworks/kio': '@latest-kf6'
'frameworks/kwindowsystem': '@latest-kf6'
'frameworks/kstatusnotifieritem': '@latest-kf6'
'frameworks/kcrash': '@latest-kf6'
- 'on': ['Linux', 'FreeBSD']
'require':
'frameworks/kdbusaddons': '@latest-kf6'

View File

@@ -7,15 +7,15 @@
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}")
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
set(KF_MIN_VERSION "6.17")
set(QT_MIN_VERSION "6.9")
set(QT_MIN_VERSION "6.8")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
@@ -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 IconThemes)
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
@@ -106,7 +107,7 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE AND NOT HAIKU)
find_package(KF6DBusAddons ${KF_MIN_VERSION} REQUIRED)
endif()
find_package(QuotientQt6 0.9.3)
find_package(QuotientQt6 0.9.1)
set_package_properties(QuotientQt6 PROPERTIES
TYPE REQUIRED
DESCRIPTION "Qt wrapper around Matrix API"

View File

@@ -63,7 +63,7 @@ void ActionsTest::testActions_data()
QTest::addColumn<std::optional<QString>>("resultText");
QTest::addColumn<std::optional<Quotient::RoomMessageEvent::MsgType>>("type");
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\\\_(ツ)\\_/¯ Hello"_s)
QTest::newRow("shrug") << u"/shrug Hello"_s << std::make_optional(u"¯\\\\_(ツ)_/¯ Hello"_s)
<< std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("lenny") << u"/lenny Hello"_s << std::make_optional(u"( ͡° ͜ʖ ͡°) Hello"_s) << std::make_optional(Quotient::RoomMessageEvent::MsgType::Text);
QTest::newRow("tableflip") << u"/tableflip Hello"_s << std::make_optional(u"(╯°□°)╯︵ ┻━┻ Hello"_s)

View File

@@ -28,7 +28,6 @@ private:
private Q_SLOTS:
void initTestCase();
void testMaximizeMedia();
void testResolveMatrixLinks();
};
void RoomManagerTest::initTestCase()
@@ -129,13 +128,5 @@ void RoomManagerTest::testMaximizeMedia()
QVERIFY(spy[1][0] == 0);
}
void RoomManagerTest::testResolveMatrixLinks()
{
// Test if resolving a non-joined room will bring up the confirmation dialog.
const QSignalSpy askToJoinSpy(&RoomManager::instance(), &RoomManager::askJoinRoom);
RoomManager::instance().resolveResource(QStringLiteral("matrix:r/testbuild:matrix.org"), QStringLiteral("join"));
QTRY_COMPARE(askToJoinSpy.size(), 1);
}
QTEST_MAIN(RoomManagerTest)
#include "roommanagertest.moc"

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>

View File

@@ -208,7 +208,7 @@ void TimelineMessageModelTest::idToRow()
auto room = new TestUtils::TestRoom(connection, u"#myroom:kde.org"_s, u"test-min-sync.json"_s);
model->setRoom(room);
QCOMPARE(model->indexForEventId(u"$153456789:example.org"_s).row(), 0);
QCOMPARE(model->indexforEventId(u"$153456789:example.org"_s).row(), 0);
}
void TimelineMessageModelTest::cleanup()

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>
@@ -50,6 +49,7 @@
<name xml:lang="ta">நியோச்சாட்</name>
<name xml:lang="tr">NeoChat</name>
<name xml:lang="uk">NeoChat</name>
<name xml:lang="x-test">xxNeoChatxx</name>
<name xml:lang="zh-CN">NeoChat</name>
<name xml:lang="zh-TW">NeoChat</name>
<summary>Chat on Matrix</summary>
@@ -76,7 +76,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>
@@ -84,6 +83,7 @@
<summary xml:lang="ta">மேட்ரிக்ஸுக்கான உரையாடல் செயலி</summary>
<summary xml:lang="tr">Matrix Üzerinde Sohbet</summary>
<summary xml:lang="uk">Спілкування у Matrix</summary>
<summary xml:lang="x-test">xxChat on Matrixxx</summary>
<summary xml:lang="zh-TW">在 Matrix 上聊天</summary>
<description>
<p>NeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.</p>
@@ -111,13 +111,13 @@
<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>
<p xml:lang="sv">NeoChat är ett chattprogram som låter dig dra full nytta av Matrix-nätverket. Det ger dig ett säkert sätt att skicka textmeddelanden, videor och ljudfiler till din familj, kollegor och vänner.</p>
<p xml:lang="tr">NeoChat, Matrix ağının tüm özelliklerini kullanan bir sohbet uygulamasıdır. Ailenize, arkadaşlarınıza ve iş arkadaşlarınıza metin iletileri, ses ve video dosyaları göndermenin kolay bir yolunu sunar.</p>
<p xml:lang="uk">NeoChat є програмою для спілкування, за допомогою якої ви можете скористатися усіма перевагами мережі Matrix. За її допомогою ви можете безпечно надсилати текстові повідомлення, відео та звукові файли вашим родичам, колегам та друзям.</p>
<p xml:lang="x-test">xxNeoChat is a chat app that lets you take full advantage of the Matrix network. It provides you with a secure way to send text messages, videos and audio files to your family, colleagues and friends.xx</p>
<p xml:lang="zh-TW">NeoChat 是一個讓您能夠完全利用 Matrix 網路的聊天應用程式。它讓您安全地傳送文字訊息、影片或音訊檔給家人、同事或朋友等等。</p>
<p>NeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.</p>
<p xml:lang="ar">يهدف نيوتشات إلى أن يكون تطبيقًا كامل الميزات لمواصفات ماتركس. يوفر نيوتشات كل شيء في المواصفات المستقرة الحالية مع الاستثناءات الملحوظة لـ VoIP و تعدد الخيوط وبعض جوانب التعمية من طرف إلى طرف. هناك عدد قليل من الإغفالات الصغيرة الأخرى بسبب حقيقة أن مواصفات ماتركس تتطور باستمرار، ولكن يبقى الهدف توفير تطبيق للمواصفات بأكملها.</p>
@@ -145,13 +145,13 @@
<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>
<p xml:lang="sv">NeoChat har som mål att vara ett fullständigt program enligt Matrix-specifikationen. Som sådant stöds allt i den nuvarande stabila specifikationen, med de nämnvärda undantagen VoIP, trådar och några aspekter av kryptering hela vägen. Det finns några ytterligare utelämnanden på grund av att Matrix-specifikationen hela tiden utvecklas, men målet förblir att till slut erbjuda stöd för hela specifikationen.</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifrelemenin bazı yönleri gibi dikkate değer istisnalar dışında var olan kararlı belirtimdeki her şey desteklenir. Matrix belirtiminin sürekli gelişmesi nedeniyle birkaç küçük eksiklik daha var; ancak amaç tüm belirtim için nihai destek sağlamak olmayı sürdürüyor.</p>
<p xml:lang="uk">Метою створення NeoChat є повноцінна реалізація програми для специфікації Matrix. Як наслідок, реалізовано усе у поточній стабільній специфікації, окрім голосового інтернет-зв'язку, потоків та деяких аспектів міжвузлового шифрування. Є також декілька інших незначних прогалин через те, що специфікація Matrix постійно змінюється, але метою лишається повна підтримка специфікації.</p>
<p xml:lang="x-test">xxNeoChat aims to be a fully featured application for the Matrix specification. As such everything in the current stable specification with the notable exceptions of VoIP, threads and some aspects of End-to-End Encryption are supported. There are a few other smaller omissions due to the fact that the Matrix spec is constantly evolving but the aim remains to provide eventual support for the entire spec.xx</p>
<p xml:lang="zh-TW">NeoChat 以完整支援 Matrix 標準為目標,因此目前穩定版標準除了 VoIP、對話串與端對端加密的某些部分以外的所有部分都有支援。其他部分還有一些較小的不支援的部分這是因為 Matrix 標準隨時都在改進,但目標仍然時最終提供整個標準的完整支援。</p>
<p>Due to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:</p>
<p xml:lang="ar">نظرًا لطبيعة تطوير مواصفات ماتركس، يوفر نيوتشات أيضًا العديد من الميزات غير المستقرة وهي:</p>
@@ -179,7 +179,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>
@@ -187,6 +186,7 @@
<p xml:lang="ta">மேட்ரிக்ஸு நெறிமுறை வரையறுக்கப்படும் வித‍த்தின் காரணமாக, பல நிலையற்ற அம்சங்களையும் நியோச்சாட் ஆதரிக்கிறது. தற்போது ஆதரிக்கப்படுபவை:</p>
<p xml:lang="tr">NeoChat, Matrix belirtimi geliştirmesinin doğası gereği çok sayıda kararsız özelliği de destekler. Şu anda bunlar:</p>
<p xml:lang="uk">Через природу розробки специфікації Matrix, у NeoChat також передбачено підтримку численних нестабільних можливостей. У поточній версії цими можливостями є:</p>
<p xml:lang="x-test">xxDue to the nature of the Matrix specification development NeoChat also supports numerous unstable features. Currently these are:xx</p>
<p xml:lang="zh-TW">由於 Matrix 標準的開發流程的緣故NeoChat 也支援數個非穩定版的功能。目前這些功能是:</p>
<ul>
<li>Polls - MSC3381</li>
@@ -214,7 +214,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>
@@ -222,6 +221,7 @@
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
<li xml:lang="tr">Anketler — MSC3381</li>
<li xml:lang="uk">Опитування - MSC3381</li>
<li xml:lang="x-test">xxPolls - MSC3381xx</li>
<li xml:lang="zh-TW">投票 - MSC3381</li>
<li>Sticker Packs - MSC2545</li>
<li xml:lang="ar">حزم الملصقات - MSC2545</li>
@@ -248,7 +248,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>
@@ -256,6 +255,7 @@
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
<li xml:lang="tr">Çıkartma Paketleri — MSC2545</li>
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
<li>Location Events - MSC3488</li>
<li xml:lang="ar">موقع الأحداث - MSC3488</li>
@@ -282,7 +282,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>
@@ -290,6 +289,7 @@
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
<li xml:lang="tr">Konum Etkinlikleri — MSC3488</li>
<li xml:lang="uk">Місцеві зустрічі - MSC3488</li>
<li xml:lang="x-test">xxLocation Events - MSC3488xx</li>
<li xml:lang="zh-TW">位置事件 - MSC3488</li>
</ul>
</description>
@@ -320,6 +320,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 +352,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>
@@ -359,6 +359,7 @@
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot>
<screenshot type="default">
@@ -388,7 +389,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>
@@ -396,6 +396,7 @@
<caption xml:lang="ta">மேட்ரிக்ஸு இடங்களின் மூலம் புதிய சமூகங்களைக் கண்டுபிடிக்கலாம்</caption>
<caption xml:lang="tr">Matrix Alanlar ile yeni topluluklar keşfedin</caption>
<caption xml:lang="uk">Пошук нових спільнот за допомогою Matrix Spaces</caption>
<caption xml:lang="x-test">xxDiscover new communities with Matrix Spacesxx</caption>
<caption xml:lang="zh-TW">利用 Matrix 聊天空間發現新的社群</caption>
</screenshot>
<!--
@@ -433,7 +434,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>
@@ -441,6 +441,7 @@
<caption xml:lang="ta">அரங்குப்பட்டியல், உரையாடல், மற்றும் அரங்குவிவரங்களைக் கொண்டுள்ள பிரதான காட்சி</caption>
<caption xml:lang="tr">Oda listesini, sohbet penceresini ve oda bilgisini gösteren ana görünüm</caption>
<caption xml:lang="uk">Головна панель із списком кімнат, спілкуванням та даними щодо кімнати</caption>
<caption xml:lang="x-test">xxMain view with room list, chat, and room informationxx</caption>
<caption xml:lang="zh-TW">主頁面,包含聊天室列表、聊天內容,與聊天室資訊</caption>
</screenshot>
<screenshot environment="windows">
@@ -472,7 +473,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>
@@ -480,6 +480,7 @@
<caption xml:lang="ta">நுழைவுத் திரை</caption>
<caption xml:lang="tr">Oturum açma ekranı</caption>
<caption xml:lang="uk">Вікно входу</caption>
<caption xml:lang="x-test">xxLogin screenxx</caption>
<caption xml:lang="zh-TW">登入畫面</caption>
</screenshot>
</screenshots>
@@ -487,11 +488,6 @@
<content_attribute id="social-chat">intense</content_attribute>
</content_rating>
<releases>
<release version="25.12.1" date="2026-01-08"/>
<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"/>
<release version="25.04.2" date="2025-06-05"/>

View File

@@ -44,6 +44,7 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
GenericName=Matrix Client
@@ -87,6 +88,7 @@ GenericName[sv]=Matrix-klient
GenericName[ta]=Matrix வாங்கி
GenericName[tr]=Matrix İstemcisi
GenericName[uk]=Клієнт Matrix
GenericName[x-test]=xxMatrix Clientxx
GenericName[zh_CN]=Matrix 客户端
GenericName[zh_TW]=Matrix 用戶端
Comment=Chat on Matrix
@@ -108,12 +110,10 @@ Comment[ia]=Conversation en ditecto sur Matrix
Comment[it]= su Matrix
Comment[ka]=ჩატი Matrix-ზე
Comment[ko]=Matrix에서 대화하기
Comment[lt]=Pokalbiai per Matrix
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
@@ -121,6 +121,7 @@ Comment[sv]=Chatta på Matrix
Comment[ta]=மேட்ரிக்ஸில் உரையாட உதவும்
Comment[tr]=Matrix üzerinde sohbet edin
Comment[uk]=Спілкування у Matrix
Comment[x-test]=xxChat on Matrixxx
Comment[zh_CN]=在 Matrix 上聊天
Comment[zh_TW]=在 Matrix 上聊天
MimeType=x-scheme-handler/matrix;

View File

@@ -0,0 +1,28 @@
SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
SPDX-License-Identifier: BSD-2-Clause
From dbd1cefd0f07a6942aef450f8f3e082aa3b1cc25 Mon Sep 17 00:00:00 2001
From: Tobias Fella <tobias.fella@kde.org>
Date: Sun, 17 Aug 2025 20:04:04 +0200
Subject: [PATCH] Revert "Bump KF6 dependency version"
This reverts commit 18a6ea98232b3a734905fb18eebba9cf39bf5325.
---
CMakeLists.txt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 10fe66daa..cd063113d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -14,7 +14,7 @@ set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_
project(NeoChat VERSION ${RELEASE_SERVICE_VERSION})
-set(KF_MIN_VERSION "6.17")
+set(KF_MIN_VERSION "6.12")
set(QT_MIN_VERSION "6.8")
find_package(ECM ${KF_MIN_VERSION} REQUIRED NO_MODULE)
--
2.50.1

View File

@@ -0,0 +1,123 @@
SPDX-FileCopyrightText: 2025 Tobias Fella <tobias.fella@kde.org>
SPDX-License-Identifier: BSD-2-Clause
From ca72345b8ee550be2172d8ac5e5dc9e4c2b508c9 Mon Sep 17 00:00:00 2001
From: Tobias Fella <tobias.fella@kde.org>
Date: Sun, 17 Aug 2025 20:00:08 +0200
Subject: [PATCH] Revert "Use new Kirigami builtin column resize handle"
This reverts commit de97275a387abcbca6fcb185bcbd1b69c30f5c66.
---
src/app/qml/Main.qml | 1 -
src/rooms/RoomListPage.qml | 70 +++++++++++++++++++++++++++++---------
2 files changed, 54 insertions(+), 17 deletions(-)
diff --git a/src/app/qml/Main.qml b/src/app/qml/Main.qml
index ea8955674..6eed271c1 100644
--- a/src/app/qml/Main.qml
+++ b/src/app/qml/Main.qml
@@ -45,7 +45,6 @@ Kirigami.ApplicationWindow {
showExisting: true
onConnectionChosen: root.load()
}
- columnView.columnResizeMode: pageStack.wideMode ? Kirigami.ColumnView.DynamicColumns : Kirigami.ColumnView.SingleColumn
globalToolBar.canContainHandles: true
globalToolBar {
style: Kirigami.ApplicationHeaderStyle.ToolBar
diff --git a/src/rooms/RoomListPage.qml b/src/rooms/RoomListPage.qml
index 2ac211fd5..f5586d789 100644
--- a/src/rooms/RoomListPage.qml
+++ b/src/rooms/RoomListPage.qml
@@ -17,22 +17,13 @@ import org.kde.neochat
Kirigami.Page {
id: root
- Kirigami.ColumnView.interactiveResizeEnabled: true
- Kirigami.ColumnView.minimumWidth: _private.collapsedSize + spaceDrawer.width + 1
- Kirigami.ColumnView.maximumWidth: _private.defaultWidth + spaceDrawer.width + 1
- Kirigami.ColumnView.onInteractiveResizingChanged: {
- if (!Kirigami.ColumnView.interactiveResizing && collapsed) {
- Kirigami.ColumnView.preferredWidth = root.Kirigami.ColumnView.minimumWidth;
- }
- }
- Kirigami.ColumnView.preferredWidth: _private.currentWidth + spaceDrawer.width + 1
- Kirigami.ColumnView.onPreferredWidthChanged: {
- if (width > _private.collapseWidth) {
- NeoChatConfig.collapsed = false;
- } else if (Kirigami.ColumnView.interactiveResizing) {
- NeoChatConfig.collapsed = true;
- }
- }
+ /**
+ * @brief The current width of the room list.
+ *
+ * @note Other objects can access the value but the private function makes sure
+ * that only the internal members can modify it.
+ */
+ readonly property int currentWidth: _private.currentWidth + spaceDrawer.width + 1
required property NeoChatConnection connection
@@ -40,6 +31,10 @@ Kirigami.Page {
signal search
+ onCurrentWidthChanged: pageStack.defaultColumnWidth = root.currentWidth
+ Component.onCompleted: pageStack.defaultColumnWidth = root.currentWidth
+
+
onCollapsedChanged: {
if (collapsed) {
RoomManager.sortFilterRoomTreeModel.filterText = "";
@@ -252,6 +247,49 @@ Kirigami.Page {
sourceComponent: Kirigami.Settings.isMobile ? exploreComponentMobile : userInfoDesktop
}
+ MouseArea {
+ anchors.top: parent.top
+ anchors.bottom: parent.bottom
+ parent: applicationWindow().overlay.parent
+
+ x: root.currentWidth - width / 2
+ width: Kirigami.Units.smallSpacing * 2
+ z: root.z + 1
+ enabled: RoomManager.hasOpenRoom && applicationWindow().width >= Kirigami.Units.gridUnit * 35
+ visible: enabled
+ cursorShape: Qt.SplitHCursor
+
+ property int _lastX
+
+ onPressed: mouse => {
+ _lastX = mouse.x;
+ }
+ onPositionChanged: mouse => {
+ if (_lastX == -1) {
+ return;
+ }
+ if (mouse.x > _lastX) {
+ // we moved to the right
+ if (_private.currentWidth < _private.collapseWidth && _private.currentWidth + (mouse.x - _lastX) >= _private.collapseWidth) {
+ // Here we get back directly to a more wide mode.
+ _private.currentWidth = _private.defaultWidth;
+ NeoChatConfig.collapsed = false;
+ } else if (_private.currentWidth >= _private.collapseWidth) {
+ // Increase page width
+ _private.currentWidth = Math.min(_private.defaultWidth, _private.currentWidth + (mouse.x - _lastX));
+ }
+ } else if (mouse.x < _lastX) {
+ const tmpWidth = _private.currentWidth - (_lastX - mouse.x);
+ if (tmpWidth < _private.collapseWidth) {
+ _private.currentWidth = Qt.binding(() => _private.collapsedSize);
+ NeoChatConfig.collapsed = true;
+ } else {
+ _private.currentWidth = tmpWidth;
+ }
+ }
+ }
+ }
+
Component {
id: userInfo
UserInfo {
--
2.50.1

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: CC-BY-SA-4.0
>carl@carlschwan.eu</email
></author>
<date
>20221101</date>
>2022-11-01</date>
<releaseinfo
>22.09</releaseinfo>
<productname
@@ -111,9 +111,9 @@ SPDX-License-Identifier: CC-BY-SA-4.0
><title
>Telif Hakkı</title>
<para
>Telif hakkı &copy; 20202022 Tobias Fella </para>
>Telif hakkı &copy; 2020-2022 Tobias Fella </para>
<para
>Telif hakkı &copy; 20202022 Carl Schwan </para>
>Telif hakkı &copy; 2020-2022 Carl Schwan </para>
<para
>Lisans: GNU Genel Kamu Lisansa, 3. sürüm veya sonrası &lt;<ulink url="https://www.gnu.org/licenses/gpl-3.0.html"
>https://www.gnu.org/licenses/gpl-3.0.html</ulink

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

@@ -3,7 +3,7 @@
# SPDX-FileCopyrightText: 2020-2021 Tobias Fella <tobias.fella@kde.org>
# SPDX-License-Identifier: BSD-2-Clause
qt_add_library(neochat STATIC
add_library(neochat STATIC
controller.cpp
controller.h
roommanager.cpp
@@ -35,10 +35,6 @@ qt_add_library(neochat STATIC
models/commonroomsmodel.h
texttospeechhelper.h
texttospeechhelper.cpp
models/limitermodel.cpp
models/limitermodel.h
supportcontroller.cpp
supportcontroller.h
)
set_source_files_properties(qml/OsmLocationPlugin.qml PROPERTIES
@@ -52,8 +48,6 @@ if(ANDROID OR WIN32)
set_source_files_properties(qml/GlobalMenuStub.qml PROPERTIES
QT_QML_SOURCE_TYPENAME GlobalMenu
)
else()
set(EXTRA_IMPORTS org.kde.purpose)
endif()
ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
@@ -107,9 +101,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
qml/SeenByDialog.qml
qml/SupportDialog.qml
DEPENDENCIES
QtCore
QtQuick
@@ -126,8 +117,8 @@ ecm_add_qml_module(neochat URI org.kde.neochat GENERATE_PLUGIN_SOURCE
org.kde.neochat.login
org.kde.neochat.chatbar
org.kde.config
org.kde.purpose
org.kde.syntaxhighlighting
${EXTRA_IMPORTS}
)
if(NOT ANDROID AND NOT WIN32)
@@ -146,17 +137,10 @@ if(WIN32)
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
endif()
qt_add_executable(neochat-app
add_executable(neochat-app
main.cpp
)
if(ANDROID)
set_target_properties(neochat-app PROPERTIES
OUTPUT_NAME "neochat-app"
PREFIX "lib"
)
endif()
if(TARGET Qt::WebView)
target_link_libraries(neochat-app PUBLIC Qt::WebView)
target_compile_definitions(neochat-app PUBLIC -DHAVE_WEBVIEW)
@@ -166,7 +150,6 @@ target_include_directories(neochat-app PRIVATE ${CMAKE_BINARY_DIR})
target_link_libraries(neochat-app PRIVATE
neochat
KF6::IconThemes
)
ecm_add_app_icon(NEOCHAT_ICON ICONS ${CMAKE_SOURCE_DIR}/128-logo.png)
@@ -197,7 +180,7 @@ else()
endif()
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models)
target_link_libraries(neochat PRIVATE neochatplugin Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PRIVATE Loginplugin Roomsplugin RoomInfoplugin MessageContentplugin Timelineplugin Spacesplugin Chatbarplugin Settingsplugin Devtoolsplugin)
target_link_libraries(neochat PUBLIC
LibNeoChat
Timeline
@@ -217,10 +200,8 @@ target_link_libraries(neochat PUBLIC
KF6::ConfigGui
KF6::CoreAddons
KF6::SonnetCore
KF6::IconThemes
KF6::ItemModels
KF6::I18nQml
KirigamiApp
KirigamiAddonsComponents
QuotientQt6
Login
Rooms
@@ -228,6 +209,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)
@@ -338,7 +323,6 @@ if(ANDROID)
"kt-restore-defaults-symbolic"
"user-symbolic"
"mark-location-symbolic"
"amarok_playcount"
${KIRIGAMI_ADDONS_ICONS}
)
@@ -367,8 +351,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,10 +33,13 @@
#include <KWindowSystem>
#endif
#if __has_include("KCrash")
#include <KCrash>
#endif
#include <KIconTheme>
#include <KLocalizedQmlContext>
#include <KLocalizedContext>
#include <KLocalizedString>
#include <KirigamiApp>
#include "neochat-version.h"
@@ -101,11 +104,8 @@ Q_DECL_EXPORT
#endif
int main(int argc, char *argv[])
{
QNetworkProxyFactory::setUseSystemConfiguration(true);
// We currently need to do this ourselves,
// KirigamiApp currently called this after constructing the app which breaks icons on Windows.
KIconTheme::initTheme();
QNetworkProxyFactory::setUseSystemConfiguration(true);
#ifdef HAVE_WEBVIEW
QtWebView::initialize();
@@ -113,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);
@@ -163,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;
@@ -181,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
@@ -195,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.
@@ -261,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)) {
@@ -276,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

@@ -5,7 +5,6 @@
#include "jobs/neochatgetcommonroomsjob.h"
#include <QGuiApplication>
#include <Quotient/room.h>
using namespace Quotient;
@@ -40,22 +39,8 @@ void CommonRoomsModel::setUserId(const QString &userId)
QVariant CommonRoomsModel::data(const QModelIndex &index, int roleName) const
{
auto roomId = m_commonRooms[index.row()];
auto room = connection()->room(roomId);
if (!room) {
return {};
}
switch (roleName) {
case Qt::DisplayRole:
case RoomNameRole:
return room->displayName();
case RoomAvatarRole:
return room->avatarUrl();
case RoomIdRole:
return roomId;
}
Q_UNUSED(index)
Q_UNUSED(roleName)
return {};
}
@@ -65,15 +50,6 @@ int CommonRoomsModel::rowCount(const QModelIndex &parent) const
return m_commonRooms.size();
}
QHash<int, QByteArray> CommonRoomsModel::roleNames() const
{
return {
{RoomIdRole, "roomId"},
{RoomNameRole, "roomName"},
{RoomAvatarRole, "roomAvatar"},
};
}
void CommonRoomsModel::reload()
{
if (!m_connection || m_userId.isEmpty()) {

View File

@@ -24,9 +24,7 @@ class CommonRoomsModel : public QAbstractListModel
public:
enum Roles {
RoomIdRole = Qt::UserRole,
RoomNameRole,
RoomAvatarRole,
RoomIdRole = Qt::DisplayRole,
};
Q_ENUM(Roles)
@@ -41,8 +39,6 @@ public:
[[nodiscard]] QVariant data(const QModelIndex &index, int roleName) const override;
[[nodiscard]] Q_INVOKABLE int rowCount(const QModelIndex &parent = {}) const override;
QHash<int, QByteArray> roleNames() const override;
Q_SIGNALS:
void connectionChanged();
void userIdChanged();

View File

@@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#include "models/limitermodel.h"
LimiterModel::LimiterModel(QObject *parent)
: QSortFilterProxyModel(parent)
{
connect(this, &QSortFilterProxyModel::rowsInserted, this, &LimiterModel::extraCountChanged);
connect(this, &QSortFilterProxyModel::rowsRemoved, this, &LimiterModel::extraCountChanged);
connect(this, &QSortFilterProxyModel::modelReset, this, &LimiterModel::extraCountChanged);
}
int LimiterModel::maximumCount() const
{
return m_maximumCount;
}
void LimiterModel::setMaximumCount(int maximumCount)
{
if (m_maximumCount != maximumCount) {
m_maximumCount = maximumCount;
Q_EMIT maximumCountChanged();
}
}
int LimiterModel::extraCount() const
{
if (sourceModel()) {
return std::max(sourceModel()->rowCount() - maximumCount(), 0);
}
return 0;
}
bool LimiterModel::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const
{
Q_UNUSED(source_parent)
return source_row < maximumCount();
}
#include "moc_limitermodel.cpp"

View File

@@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
#pragma once
#include <QQmlEngine>
#include <QSortFilterProxyModel>
/**
* @class LimiterModel
*
* @brief Takes a source QAbstractItemModel model and only displays a desired maximum amount.
*
* Also gives you the remaining (filtered out) items, useful for sticking in a label or somesuch.
*/
class LimiterModel : public QSortFilterProxyModel
{
Q_OBJECT
QML_ELEMENT
Q_PROPERTY(int maximumCount READ maximumCount WRITE setMaximumCount NOTIFY maximumCountChanged)
Q_PROPERTY(int extraCount READ extraCount NOTIFY extraCountChanged)
public:
explicit LimiterModel(QObject *parent = nullptr);
[[nodiscard]] int maximumCount() const;
void setMaximumCount(int maximumCount);
[[nodiscard]] int extraCount() const;
Q_SIGNALS:
void maximumCountChanged();
void extraCountChanged();
protected:
bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
private:
int m_maximumCount = 0;
};

View File

@@ -58,7 +58,7 @@ QVariant NotificationsModel::data(const QModelIndex &index, int role) const
QHash<int, QByteArray> NotificationsModel::roleNames() const
{
return {
{TextRole, "notificationText"},
{TextRole, "text"},
{RoomIdRole, "roomId"},
{AuthorName, "authorName"},
{AuthorAvatar, "authorAvatar"},

View File

@@ -42,6 +42,7 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
DesktopEntry=org.kde.neochat
@@ -86,6 +87,7 @@ Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokolle
Comment[ta]=மையமில்லா தகவல் பரிமாற்ற நெறிமுறையான மேட்ரிக்ஸுக்கான செயலி
Comment[tr]=Merkezi olmayan iletişim protokolü Matrix için bir istemci
Comment[uk]=Клієнт matrix, децентралізованого протоколу обміну даними
Comment[x-test]=xxA client for matrix, the decentralized communication protocolxx
Comment[zh_CN]=分布式通讯协议 Matrix 的客户端
Comment[zh_TW]=去中心化通訊協定 Matrix 的用戶端
@@ -132,6 +134,7 @@ Name[sv]=Nytt meddelande
Name[ta]=புதிய செய்தி
Name[tr]=Yeni İleti
Name[uk]=Нове повідомлення
Name[x-test]=xxNew messagexx
Name[zh_CN]=新消息
Name[zh_TW]=新訊息
Comment=There is a new message
@@ -174,6 +177,7 @@ Comment[sv]=Det finns ett nytt meddelande
Comment[ta]=ஒரு புதிய செய்தி உள்ளது
Comment[tr]=Yeni bir ileti var
Comment[uk]=Надійшло нове повідомлення
Comment[x-test]=xxThere is a new messagexx
Comment[zh_CN]=有新消息
Comment[zh_TW]=有新的訊息
Action=Popup
@@ -211,7 +215,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
@@ -219,6 +222,7 @@ Name[sv]=Ny inbjudan
Name[ta]=புதிய அழைப்பிதழ்
Name[tr]=Yeni Davet
Name[uk]=Нове запрошення
Name[x-test]=xxNew Invitationxx
Name[zh_CN]=新邀请
Name[zh_TW]=新邀請
Comment=There is a new invitation to a room
@@ -253,14 +257,14 @@ 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[x-test]=xxThere is a new invitation to a roomxx
Comment[zh_CN]=有新的聊天室邀请
Comment[zh_TW]=有新的加入聊天室邀請
Action=Popup
@@ -287,13 +291,11 @@ Name[ia]=Comparti
Name[it]=Condivisione
Name[ka]=გაზიარება
Name[ko]=공유
Name[lt]=Bendrinti
Name[lv]=Kopīgot
Name[nl]=Gedeelde
Name[nn]=Del
Name[pl]=Udostępnij
Name[pt_BR]=Compartilhar
Name[ro]=Partajare
Name[ru]=Публикация
Name[sa]=संविभागः
Name[sl]=Deli
@@ -301,6 +303,7 @@ Name[sv]=Dela
Name[ta]=பகிர்
Name[tr]=Paylaş
Name[uk]=Оприлюднення
Name[x-test]=xxSharexx
Name[zh_CN]=分享
Name[zh_TW]=分享
Comment=The result of sharing a piece of content
@@ -323,13 +326,11 @@ Comment[ia]=Le exito de compartir un pecietta de contento
Comment[it]=Il risultato della condivisione di un contenuto
Comment[ka]=შემცველობის ნაწილის გაზიარების შედეგი
Comment[ko]=콘텐츠 공유 결과
Comment[lt]=Turinio dalies bendrinimo rezultatas
Comment[lv]=Satura kopīgošanas rezultāts
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
@@ -337,6 +338,7 @@ Comment[sv]=Resultatet av att dela innehåll
Comment[ta]=எதையோ பகிர்ந்த‍தன் விளைவு
Comment[tr]=Bir parça içerik paylaşımının sonucu
Comment[uk]=Результат оприлюднення даних
Comment[x-test]=xxThe result of sharing a piece of contentxx
Comment[zh_CN]=分享一个内容得到的结果
Comment[zh_TW]=分享一份內容之後的結果
Action=Popup

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

@@ -11,7 +11,6 @@
#include <KNotification>
#include <KNotificationPermission>
#include <KNotificationReplyAction>
#include <KirigamiAddons/Components/NameUtils>
#include <QPainter>
#include <Quotient/accountregistry.h>
@@ -145,9 +144,16 @@ void NotificationsManager::processNotificationJob(QPointer<NeoChatConnection> co
body = notification["event"_L1]["content"_L1]["body"_L1].toString();
}
QImage avatar_image;
if (!sender.avatarUrl().isEmpty()) {
avatar_image = room->member(sender.id()).avatar(128, 128, {});
} else {
avatar_image = room->avatar(128);
}
postNotification(dynamic_cast<NeoChatRoom *>(room),
room->member(sender.id()),
sender.displayName(),
body,
avatar_image,
notification["event"_L1].toObject()["event_id"_L1].toString(),
true,
pair.first);
@@ -189,8 +195,9 @@ bool NotificationsManager::shouldPostNotification(QPointer<NeoChatConnection> co
}
void NotificationsManager::postNotification(NeoChatRoom *room,
const RoomMember &member,
const QString &sender,
const QString &text,
const QImage &icon,
const QString &replyEventId,
bool canReply,
qint64 timestamp)
@@ -209,17 +216,17 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
}
});
notification->setTitle(room->displayName());
QString entry;
if (room->isDirectChat()) {
if (sender == room->displayName()) {
notification->setTitle(sender);
entry = text.toHtmlEscaped();
} else {
entry = i18n("%1: %2", member.displayName(), text.toHtmlEscaped());
notification->setTitle(room->displayName());
entry = i18n("%1: %2", sender, text.toHtmlEscaped());
}
notification->setText(entry);
notification->setPixmap(createNotificationImage(member, room));
notification->setPixmap(createNotificationImage(icon, room));
auto defaultAction = notification->addDefaultAction(i18n("Open NeoChat in this room"));
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
@@ -246,9 +253,7 @@ void NotificationsManager::postNotification(NeoChatRoom *room,
notification->setReplyAction(std::move(replyAction));
}
if (Controller::instance().accounts()->rowCount() > 1) {
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
}
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
notification->sendEvent();
}
@@ -287,10 +292,18 @@ void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
}
const auto sender = room->member(roomMemberEvent->senderId());
QImage avatar_image;
if (roomMemberEvent && !room->member(roomMemberEvent->senderId()).avatarUrl().isEmpty()) {
avatar_image = room->member(roomMemberEvent->senderId()).avatar(128, 128, {});
} else {
qWarning() << "using this room's avatar";
avatar_image = room->avatar(128);
}
KNotification *notification = new KNotification(u"invite"_s);
notification->setText(i18n("%1 invited you to a room", sender.htmlSafeDisplayName()));
notification->setTitle(room->displayName());
notification->setPixmap(createNotificationImage(sender, room));
notification->setPixmap(createNotificationImage(avatar_image, nullptr));
auto defaultAction = notification->addDefaultAction(i18n("Open this invitation in NeoChat"));
connect(defaultAction, &KNotificationAction::activated, this, [notification, room]() {
if (!room) {
@@ -334,9 +347,7 @@ void NotificationsManager::doPostInviteNotification(QPointer<NeoChatRoom> room)
m_invitations.remove(room->id());
});
if (Controller::instance().accounts()->rowCount() > 1) {
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
}
notification->setHint(u"x-kde-origin-name"_s, room->localMember().id());
notification->sendEvent();
}
@@ -377,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());
@@ -391,130 +402,48 @@ 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;
}
}
QPixmap NotificationsManager::createNotificationImage(const Quotient::RoomMember &member, NeoChatRoom *room)
{
QImage senderIcon = member.avatar(avatarDimension, avatarDimension, {});
bool senderIconIsPlaceholder = false;
if (senderIcon.isNull()) {
senderIcon = createPlaceholderImage(member.displayName());
senderIconIsPlaceholder = true;
}
QImage icon;
if (room->isDirectChat()) {
icon = senderIcon;
} else {
QImage roomIcon = room->avatar(avatarDimension, avatarDimension);
bool roomIconIsPlaceholder = false;
if (roomIcon.isNull()) {
roomIcon = createPlaceholderImage(room->displayName());
roomIconIsPlaceholder = true;
}
icon = createCombinedNotificationImage(senderIcon, senderIconIsPlaceholder, roomIcon, roomIconIsPlaceholder);
}
return QPixmap::fromImage(icon);
}
QImage NotificationsManager::createCombinedNotificationImage(const QImage &senderIcon,
const bool senderIconIsPlaceholder,
const QImage &roomIcon,
const bool roomIconIsPlaceholder)
QPixmap NotificationsManager::createNotificationImage(const QImage &icon, NeoChatRoom *room)
{
// Handle avatars that are lopsided in one dimension
const int biggestDimension = std::max(senderIcon.width(), senderIcon.height());
const QRectF imageRect = QRect{0, 0, biggestDimension, biggestDimension}.toRectF();
const int biggestDimension = std::max(icon.width(), icon.height());
const QRect imageRect{0, 0, biggestDimension, biggestDimension};
QImage roundedImage(imageRect.size().toSize(), QImage::Format_ARGB32);
QImage roundedImage(imageRect.size(), QImage::Format_ARGB32);
roundedImage.fill(Qt::transparent);
QPainter painter(&roundedImage);
painter.setRenderHint(QPainter::SmoothPixmapTransform);
painter.setPen(Qt::NoPen);
if (senderIconIsPlaceholder) {
painter.drawImage(imageRect, senderIcon);
} else {
// Fill background for transparent non-placeholder avatars
painter.setBrush(Qt::white);
painter.drawRoundedRect(imageRect, imageRect.width(), imageRect.height());
// Fill background for transparent avatars
painter.setBrush(Qt::white);
painter.drawRoundedRect(imageRect, imageRect.width(), imageRect.height());
painter.setBrush(senderIcon.scaledToHeight(biggestDimension));
painter.drawRoundedRect(imageRect, imageRect.width(), imageRect.height());
QBrush brush(icon.scaledToHeight(biggestDimension));
painter.setBrush(brush);
painter.drawRoundedRect(imageRect, imageRect.width(), imageRect.height());
if (room != nullptr) {
const QImage roomAvatar = room->avatar(imageRect.width(), imageRect.height());
if (!roomAvatar.isNull() && icon != roomAvatar) {
const QRect lowerQuarter{imageRect.center(), imageRect.size() / 2};
painter.setBrush(Qt::white);
painter.drawRoundedRect(lowerQuarter, lowerQuarter.width(), lowerQuarter.height());
painter.setBrush(roomAvatar.scaled(lowerQuarter.size()));
painter.drawRoundedRect(lowerQuarter, lowerQuarter.width(), lowerQuarter.height());
}
}
const QRectF lowerQuarter{imageRect.center(), imageRect.size() / 2.0};
if (roomIconIsPlaceholder) {
// Ditto for room icons, but we also want to "carve out" the transparent area for readability
painter.setCompositionMode(QPainter::CompositionMode_Clear);
painter.setBrush(Qt::transparent);
painter.drawRoundedRect(lowerQuarter, lowerQuarter.width(), lowerQuarter.height());
painter.setCompositionMode(QPainter::CompositionMode_SourceOver);
painter.drawImage(lowerQuarter, roomIcon);
} else {
painter.setBrush(Qt::white);
painter.drawRoundedRect(lowerQuarter, lowerQuarter.width(), lowerQuarter.height());
painter.setBrush(roomIcon.scaled(lowerQuarter.size().toSize()));
painter.drawRoundedRect(lowerQuarter, lowerQuarter.width(), lowerQuarter.height());
}
return roundedImage;
}
QImage NotificationsManager::createPlaceholderImage(const QString &name)
{
const QColor color = NameUtils().colorsFromString(name);
QImage image(avatarDimension, avatarDimension, QImage::Format_ARGB32);
image.fill(Qt::transparent);
QPainter painter(&image);
painter.setRenderHint(QPainter::Antialiasing);
// Draw background
QColor backgroundColor = color;
backgroundColor.setAlphaF(0.07); // Same as in Kirigami Add-ons.
painter.setBrush(backgroundColor);
painter.setPen(Qt::transparent);
painter.drawRoundedRect(image.rect(), image.width(), image.height());
constexpr float borderWidth = 3.0; // Slightly bigger than in Add-ons so it renders better with QPainter at these dimensions.
// Draw border
painter.setBrush(Qt::transparent);
painter.setPen(QPen(color, borderWidth));
painter.drawRoundedRect(image.rect().toRectF().marginsRemoved(QMarginsF(borderWidth, borderWidth, borderWidth, borderWidth)),
image.width(),
image.height());
const QString initials = NameUtils().initialsFromString(name);
QTextOption option;
option.setAlignment(Qt::AlignCenter);
// Calculation similar to the one found in Kirigami Add-ons.
constexpr int largeSpacing = 8; // Same as what's defined in kirigami.
constexpr int padding = std::max(0, std::min(largeSpacing, avatarDimension - largeSpacing * 2));
QFont font;
font.setPixelSize((avatarDimension - padding) / 2);
painter.setBrush(color);
painter.setPen(color);
painter.setFont(font);
painter.drawText(image.rect(), initials, option);
return image;
return QPixmap::fromImage(roundedImage);
}
#include "moc_notificationsmanager.cpp"

View File

@@ -13,11 +13,6 @@
#include <Quotient/csapi/notifications.h>
#include <Quotient/jobs/basejob.h>
namespace Quotient
{
class RoomMember;
}
class NeoChatConnection;
class KNotification;
class NeoChatRoom;
@@ -58,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.
@@ -72,24 +67,15 @@ private:
QStringList m_connActiveJob;
void startNotificationJob(QPointer<NeoChatConnection> connection);
/**
* @return A combined image of the sender and room's avatar.
*/
static QPixmap createNotificationImage(const Quotient::RoomMember &member, NeoChatRoom *room);
/**
* @return The sender and room icon combined together into one image. Used internally by createNotificationImage.
*/
static QImage createCombinedNotificationImage(const QImage &senderIcon, bool senderIconIsPlaceholder, const QImage &roomIcon, bool roomIconIsPlaceholder);
/**
* @return A placeholder avatar image, similar to the one found in Kirigami Add-ons.
*/
static QImage createPlaceholderImage(const QString &name);
QPixmap createNotificationImage(const QImage &icon, NeoChatRoom *room);
bool shouldPostNotification(QPointer<NeoChatConnection> connection, const QJsonValue &notification);
void
postNotification(NeoChatRoom *room, const Quotient::RoomMember &member, const QString &text, const QString &replyEventId, bool canReply, qint64 timestamp);
void postNotification(NeoChatRoom *room,
const QString &sender,
const QString &text,
const QImage &icon,
const QString &replyEventId,
bool canReply,
qint64 timestamp);
void doPostInviteNotification(QPointer<NeoChatRoom> room);
@@ -98,8 +84,6 @@ private:
bool permissionAsked = false;
static constexpr int avatarDimension = 128;
private Q_SLOTS:
void processNotificationJob(QPointer<NeoChatConnection> connection, Quotient::GetNotificationsJob *job, bool initialization);
};

View File

@@ -43,6 +43,7 @@ Name[sv]=NeoChat
Name[ta]=நியோச்சாட்
Name[tr]=NeoChat
Name[uk]=NeoChat
Name[x-test]=xxNeoChatxx
Name[zh_CN]=NeoChat
Name[zh_TW]=NeoChat
Comment=Find rooms in NeoChat
@@ -75,7 +76,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
@@ -83,6 +83,7 @@ Comment[sv]=Sök efter rum i NeoChat
Comment[ta]=நியோச்சாட்டில் அரங்குகளை கண்டுபிடிக்கும்
Comment[tr]=NeoChatte odalar bulun
Comment[uk]=Пошук кімнат у NeoChat
Comment[x-test]=xxFind rooms in NeoChatxx
Comment[zh_CN]=在 NeoChat 查找聊天室
Comment[zh_TW]=在 NeoChat 尋找聊天室
X-KDE-ServiceTypes=Plasma/Runner

View File

@@ -5,7 +5,6 @@ pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtMultimedia
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
@@ -19,10 +18,6 @@ KirigamiComponents.ConvergentContextMenu {
required property NeoChatConnection connection
required property Kirigami.ApplicationWindow window
data: MediaDevices {
id: devices
}
Kirigami.Action {
text: i18nc("@action:button", "Show QR Code")
icon.name: "view-barcode-qr-symbolic"
@@ -38,14 +33,12 @@ KirigamiComponents.ConvergentContextMenu {
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Scan a QR Code")
icon.name: "document-scan-symbolic"
visible: devices.videoInputs.length > 0
onTriggered: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent("org.kde.neochat", "QrScannerPage"), {
text: i18nc("@action:inmenu", "Switch Account")
icon.name: "system-switch-user"
shortcut: "Ctrl+U"
onTriggered: (Qt.createComponent("org.kde.neochat", "AccountSwitchDialog").createObject(QQC2.Overlay.overlay, {
connection: root.connection
}, {
title: i18nc("@title", "Scan a QR Code")
})
}) as Kirigami.Dialog).open();
}
Kirigami.Action {
@@ -62,6 +55,14 @@ KirigamiComponents.ConvergentContextMenu {
}
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Devices")
icon.name: "computer-symbolic"
onTriggered: {
NeoChatSettingsView.open('devices');
}
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Open Developer Tools")
icon.name: "tools"
@@ -75,10 +76,19 @@ 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")
})
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Verify This Device")
icon.name: "security-low"
visible: !root.connection.isVerifiedSession
visible: !root.connection.isVerifiedSession()
onTriggered: {
root.connection.startSelfVerification();
const dialog = Qt.createComponent("org.kde.kirigami", "PromptDialog").createObject(QQC2.Overlay.overlay, {
@@ -94,25 +104,10 @@ KirigamiComponents.ConvergentContextMenu {
}
Kirigami.Action {
text: i18nc("@action:inmenu Open support dialog", "Support")
icon.name: "help-contents-symbolic"
onTriggered: {
Qt.createComponent("org.kde.neochat", "SupportDialog").createObject(QQC2.Overlay.overlay, {
connection: root.connection,
}).open();
}
}
Kirigami.Action {
separator: true
}
Kirigami.Action {
text: i18nc("@action:inmenu", "Switch Account")
icon.name: "system-switch-user"
shortcut: "Ctrl+U"
onTriggered: (Qt.createComponent("org.kde.neochat", "AccountSwitchDialog").createObject(QQC2.Overlay.overlay, {
text: i18nc("@action:inmenu", "Logout…")
icon.name: "im-kick-user"
onTriggered: (Qt.createComponent("org.kde.neochat", "ConfirmLogoutDialog").createObject(QQC2.Overlay.overlay, {
connection: root.connection
}) as Kirigami.Dialog).open();
}) as Kirigami.Dialog).open()
}
}

View File

@@ -61,7 +61,7 @@ Kirigami.Dialog {
}
onClicked: {
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {}, {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {}, {
title: i18nc("@title:window", "Login")
});
root.close();
@@ -95,8 +95,8 @@ Kirigami.Dialog {
accountView.decrementCurrentIndex();
}
}
Keys.onEnterPressed: ((accountView.currentItem ?? accountView.footerItem) as Delegates.RoundedItemDelegate).clicked()
Keys.onReturnPressed: ((accountView.currentItem ?? accountView.footerItem) as Delegates.RoundedItemDelegate).clicked()
Keys.onEnterPressed: (accountView.currentItem as Delegates.RoundedItemDelegate).clicked()
Keys.onReturnPressed: (accountView.currentItem as Delegates.RoundedItemDelegate).clicked()
onVisibleChanged: {
for (let i = 0; i < accountView.count; i++) {

View File

@@ -48,7 +48,7 @@ Components.AbstractMaximizeComponent {
implicitWidth: Kirigami.Units.iconSizes.medium
implicitHeight: Kirigami.Units.iconSizes.medium
name: root.author.displayName
name: root.author.name ?? root.author.displayName
source: root.author.avatarUrl
color: root.author.color
}
@@ -57,7 +57,7 @@ Components.AbstractMaximizeComponent {
QQC2.Label {
id: userLabel
text: root.author.displayName
text: root.author.name ?? root.author.displayName
color: root.author.color
font.weight: Font.Bold
elide: Text.ElideRight
@@ -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

@@ -6,15 +6,16 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
ColumnLayout {
id: root
required property string emoji
required property string description
property alias emoji: emojiLabel.text
property alias description: descriptionLabel.text
QQC2.Label {
text: root.emoji
id: emojiLabel
Layout.fillWidth: true
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
@@ -24,7 +25,7 @@ ColumnLayout {
font.pointSize: Kirigami.Theme.defaultFont.pointSize * 4
}
QQC2.Label {
text: root.description
id: descriptionLabel
Layout.fillWidth: true
verticalAlignment: Text.AlignVCenter
horizontalAlignment: Text.AlignHCenter

View File

@@ -17,6 +17,9 @@ RowLayout {
Repeater {
id: repeater
delegate: EmojiItem {}
delegate: EmojiItem {
emoji: modelData.emoji
description: modelData.description
}
}
}

View File

@@ -17,7 +17,7 @@ ApplicationWindow {
property real longitude: NaN
property string asset
property var author
property LiveLocationsModel liveLocationModel: null
property QtObject liveLocationModel: null
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
visibility: Qt.WindowFullScreen
@@ -59,7 +59,7 @@ ApplicationWindow {
Connections {
target: mapView.map
function onCopyrightLinkActivated(link: string) {
function onCopyrightLinkActivated() {
Qt.openUrlExternally(link);
}
}

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 {
@@ -45,12 +45,14 @@ Labs.MenuBar {
}
Labs.MenuItem {
icon.name: "compass-symbolic"
text: i18nc("@action:inmenu Explore public rooms and spaces", "Explore")
text: i18nc("@action:inmenu", "Explore Rooms")
enabled: root.connection
onTriggered: {
let dialog = root.appWindow.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {});
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

View File

@@ -3,10 +3,8 @@
import QtQuick
import org.kde.kirigami as Kirigami
import org.kde.neochat
Item {
required property NeoChatConnection connection
required property Kirigami.ApplicationWindow appWindow
}

View File

@@ -27,17 +27,16 @@ ColumnLayout {
Layout.fillHeight: true
}
KirigamiComponents.AvatarButton {
KirigamiComponents.Avatar {
id: avatar
Layout.preferredWidth: Kirigami.Units.iconSizes.huge
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
Layout.alignment: Qt.AlignHCenter
Layout.fillWidth: true
name: root.invitingMember.displayName
source: NeoChatConfig.hideImages ? undefined : root.invitingMember.avatarUrl
color: root.invitingMember.color
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
}
Loader {
@@ -55,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
@@ -68,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
}
}
}
@@ -139,24 +137,8 @@ ColumnLayout {
Layout.fillWidth: true
FormCard.FormButtonDelegate {
id: viewProfileDelegate
icon.name: "user-properties-symbolic"
text: i18nc("@action:button View this user's profile", "View %1's Profile", root.invitingMember.displayName)
onClicked: RoomManager.resolveResource(root.currentRoom.invitingUserId)
}
FormCard.FormDelegateSeparator {
above: viewProfileDelegate
below: ignoreUserDelegate
}
FormCard.FormButtonDelegate {
id: ignoreUserDelegate
icon.name: "list-remove-symbolic"
text: i18nc("@action:button Ignore the user", "Ignore %1 and Reject Invite", root.invitingMember.displayName)
text: i18nc("@action:button Block the user", "Block %1", root.invitingMember.displayName)
onClicked: {
root.currentRoom.forget()

View File

@@ -66,7 +66,7 @@ Kirigami.Dialog {
text: i18nc("@action:button", "Join room")
icon.name: "irc-join-channel"
onClicked: {
RoomManager.resolveResource(root.room, "join_confirmed");
RoomManager.resolveResource(root.room, "join");
root.close();
}
}

View File

@@ -4,7 +4,6 @@
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import QtQuick.Window
import QtQml
import org.kde.kirigami as Kirigami
@@ -23,63 +22,72 @@ Kirigami.Page {
name: "cancelled"
when: root.session.state === KeyVerificationSession.CANCELED
PropertyChanges {
stateLoader.sourceComponent: verificationCanceled
target: stateLoader
sourceComponent: verificationCanceled
}
},
State {
name: "waitingForVerification"
when: root.session.state === KeyVerificationSession.WAITINGFORVERIFICATION
PropertyChanges {
stateLoader.sourceComponent: emojiSas
target: stateLoader
sourceComponent: emojiSas
}
},
State {
name: "waitingForReady"
when: root.session.state === KeyVerificationSession.WAITINGFORREADY
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "incoming"
when: root.session.state === KeyVerificationSession.INCOMING
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "waitingForKey"
when: root.session.state === KeyVerificationSession.WAITINGFORKEY
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "waitingForAccept"
when: root.session.state === KeyVerificationSession.WAITINGFORACCEPT
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "waitingForMac"
when: root.session.state === KeyVerificationSession.WAITINGFORMAC
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
},
State {
name: "ready"
when: root.session.state === KeyVerificationSession.READY
PropertyChanges {
stateLoader.sourceComponent: chooseVerificationComponent
target: stateLoader
sourceComponent: chooseVerificationComponent
}
},
State {
name: "done"
when: root.session.state === KeyVerificationSession.DONE
PropertyChanges {
stateLoader.sourceComponent: message
target: stateLoader
sourceComponent: message
}
}
]
@@ -162,8 +170,6 @@ Kirigami.Page {
return "";
}
}
isDone: root.session.state === KeyVerificationSession.DONE
onDone: root.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();
});
@@ -234,7 +233,7 @@ Kirigami.ApplicationWindow {
RoomListPage {
id: roomList
onSearch: root.quickSwitcher.open()
onSearch: quickSwitcher.open()
connection: root.connection
@@ -268,17 +267,6 @@ Kirigami.ApplicationWindow {
}
}
Connections {
target: root.connection
function onLoggedOut(): void {
root.pageStack.clear();
let page = root.pageStack.push(Qt.createComponent('org.kde.neochat.login', 'WelcomePage'), {
showExisting: true,
}) as WelcomePage;
page.connectionChosen.connect(() => root.load())
}
}
Connections {
target: AccountRegistry
function onRowsRemoved() {
@@ -359,11 +347,7 @@ Kirigami.ApplicationWindow {
user: user,
connection: root.connection,
}) as UserDetailDialog;
// FIXME: The reason why we don't want the focusedWindowItem for the room null case (aka QR codes) is because it will parent it to the soon-to-be-destroyed window item.
// But this won't be a problem if we turn it into a Kirigami.Dialog or some other in-scene item, which it really should be.
if (room != null) {
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
}
dialog.parent = QmlUtils.focusedWindowItem(); // Kirigami Dialogs overwrite the parent, so we need to set it again
dialog.open();
}

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

@@ -34,8 +34,8 @@ Kirigami.Page {
enabled: root.model
target: root.room
function onChanged(): void {
root.contentJson = root.model.stateEventContentJson(root.type, root.stateKey);
root.sourceText = root.model.stateEventJson(root.type, root.stateKey);
root.contentJson = model.stateEventContentJson(root.type, root.stateKey);
root.sourceText = model.stateEventJson(root.type, root.stateKey);
}
}
@@ -46,8 +46,8 @@ Kirigami.Page {
text: i18nc("@action As in 'edit the state of this room'", "Edit state")
icon.name: "document-edit"
visible: root.allowEdit
enabled: root.room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
onTriggered: (root.Kirigami.PageStack.pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog"), {
enabled: room.canSendState(root.type) && (!root.stateKey.startsWith("@") || root.stateKey === root.room.connection.localUserId) && root.type !== "m.room.create"
onTriggered: pageStack.pushDialogLayer(Qt.createComponent("org.kde.neochat", "EditStateDialog"), {
room: root.room,
type: root.type,
stateKey: root.stateKey,

View File

@@ -1,8 +1,6 @@
// SPDX-FileCopyrightText: 2023 James Graham <james.h.graham@protonmail.com>
// SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
pragma ComponentBehavior: Bound
import QtCore as Core
import QtQuick
import QtQuick.Controls as QQC2
@@ -23,45 +21,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.currentIndex, 0), TimelineMessageModel.EventIdRole)
if (idx === -1) {
return ""
}
readonly property var currentAuthor: model.data(model.index(content.currentIndex, 0), TimelineMessageModel.AuthorRole)
return model.data(model.index(idx, 0), TimelineMessageModel.EventIdRole)
}
readonly property var currentTime: model.data(model.index(content.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.currentIndex, 0), TimelineMessageModel.ProgressInfoRole)
actions: [
ShareAction {
@@ -93,28 +59,28 @@ Components.AlbumMaximizeComponent {
downloadAction: Components.DownloadAction {
onTriggered: {
root.currentRoom.downloadFile(root.currentEventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + root.currentRoom.fileNameToDownload(root.currentEventId));
currentRoom.downloadFile(root.currentEventId, Core.StandardPaths.writableLocation(Core.StandardPaths.CacheLocation) + "/" + root.currentEventId.replace(":", "_").replace("/", "_").replace("+", "_") + currentRoom.fileNameToDownload(root.currentEventId));
}
}
playAction: Kirigami.Action {
onTriggered: {
MediaManager.startPlayback();
(root.currentItem as Components.VideoMaximizeDelegate).play();
currentItem.play();
}
}
Connections {
target: MediaManager
function onPlaybackStarted() {
if ((root.currentItem as Components.VideoMaximizeDelegate).playbackState === MediaPlayer.PlayingState) {
(root.currentItem as Components.VideoMaximizeDelegate).pause();
if (currentItem.playbackState === MediaPlayer.PlayingState) {
currentItem.pause();
}
}
}
Connections {
target: root.currentRoom
target: currentRoom
function onFileTransferProgress(id, progress, total) {
if (id == root.currentEventId) {
@@ -154,13 +120,10 @@ Components.AlbumMaximizeComponent {
onOpened: forceActiveFocus()
onItemRightClicked: {
const event = root.currentRoom.findEvent(root.currentEventId);
RoomManager.viewEventMenu(root.QQC2.Overlay.overlay, event, root.currentRoom)
}
onItemRightClicked: RoomManager.viewEventMenu(root.currentEventId, root.currentRoom)
onSaveItem: {
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay) as Dialogs.FileDialog;
var dialog = saveAsDialog.createObject(QQC2.Overlay.overlay);
dialog.selectedFile = currentRoom.fileNameToDownload(root.currentEventId);
dialog.open();
}
@@ -183,7 +146,7 @@ Components.AlbumMaximizeComponent {
if (!selectedFile) {
return;
}
root.currentRoom.downloadFile(root.currentEventId, selectedFile);
currentRoom.downloadFile(root.currentEventId, selectedFile);
}
}
}

View File

@@ -1,13 +1,12 @@
// SPDX-FileCopyrightText: 2023 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
import org.kde.kirigami as Kirigami
import org.kde.kirigami.delegates as KD
import org.kde.kirigamiaddons.components as Components
import org.kde.neochat
@@ -42,23 +41,15 @@ Kirigami.ScrollablePage {
}
delegate: QQC2.ItemDelegate {
id: notificationDelegate
required property string uri
required property string authorAvatar
required property string authorName
required property string roomDisplayName
required property string notificationText
width: parent?.width ?? 0
onClicked: RoomManager.resolveResource(uri)
onClicked: RoomManager.resolveResource(model.uri)
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
Components.Avatar {
source: notificationDelegate.authorAvatar
name: notificationDelegate.authorName
source: model.authorAvatar
name: model.authorName
implicitHeight: Kirigami.Units.gridUnit + Kirigami.Units.largeSpacing * 2
implicitWidth: implicitHeight
@@ -75,7 +66,7 @@ Kirigami.ScrollablePage {
QQC2.Label {
id: label
text: notificationDelegate.roomDisplayName
text: model.roomDisplayName
elide: Text.ElideRight
font.weight: Font.Normal
textFormat: Text.PlainText
@@ -87,9 +78,10 @@ Kirigami.ScrollablePage {
QQC2.Label {
id: subtitle
text: notificationDelegate.notificationText
text: model.text
elide: Text.ElideRight
font: Kirigami.Theme.smallFont
opacity: root.hasNotifications ? 0.9 : 0.7
Layout.fillWidth: true
Layout.alignment: Qt.AlignLeft | Qt.AlignTop

View File

@@ -27,26 +27,9 @@ Kirigami.Page {
}
}
MediaDevices {
id: devices
}
Rectangle {
anchors.fill: parent
color: Kirigami.Theme.backgroundColor
visible: devices.videoInputs.length === 0
Kirigami.PlaceholderMessage {
text: i18nc("@info", "No Camera Connected")
anchors.centerIn: parent
}
}
VideoOutput {
id: viewFinder
anchors.centerIn: parent
visible: devices.videoInputs.length > 0
}
Prison.VideoScanner {
@@ -64,8 +47,6 @@ Kirigami.Page {
}
CaptureSession {
id: session
camera: Camera {
id: camera
}

View File

@@ -30,13 +30,15 @@ Kirigami.SearchDialog {
emptyText: i18nc("Placeholder message", "No room found")
Kirigami.Action {
id: exploreRoomAction
text: i18nc("@action:button Explore public rooms and spaces", "Explore")
text: i18nc("@action:button", "Explore rooms")
icon.name: "compass"
onTriggered: {
root.close()
let dialog = root.window.pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ExploreRoomsPage'), {
connection: root.connection
}, {});
}, {
title: i18nc("@title", "Explore Rooms")
});
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
RoomManager.resolveResource(roomId.length > 0 ? roomId : alias, isJoined ? "" : "join");
});

View File

@@ -6,7 +6,6 @@ import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.neochat
Kirigami.Page {
id: root
@@ -14,8 +13,6 @@ Kirigami.Page {
required property string placeholder
required property string actionText
required property string icon
required property bool reporting
required property NeoChatConnection connection
signal accepted(reason: string)
@@ -24,15 +21,6 @@ Kirigami.Page {
topPadding: 0
bottomPadding: 0
header: Kirigami.InlineMessage {
showCloseButton: false
visible: root.reporting
type: Kirigami.MessageType.Information
position: Kirigami.InlineMessage.Position.Header
text: xi18n("This report will <strong>only</strong> be sent to the administrators of <link>%1</link> (your server).", root.connection.domain)
}
QQC2.TextArea {
id: reason
placeholderText: root.placeholder
@@ -43,7 +31,7 @@ Kirigami.Page {
Keys.onReturnPressed: event => {
if (event.modifiers & Qt.ControlModifier) {
root.accepted(reason.text);
root.Kirigami.PageStack.closeDialog();
root.closeDialog();
}
}
@@ -64,14 +52,14 @@ Kirigami.Page {
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.AcceptRole
onClicked: {
root.accepted(reason.text);
root.Kirigami.PageStack.closeDialog();
root.closeDialog();
}
}
QQC2.Button {
icon.name: "dialog-cancel-symbolic"
text: i18nc("@action", "Cancel")
QQC2.DialogButtonBox.buttonRole: QQC2.DialogButtonBox.RejectRole
onClicked: root.Kirigami.PageStack.closeDialog()
onClicked: root.closeDialog()
}
}
}

View File

@@ -2,12 +2,9 @@
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
// SPDX-License-Identifier: GPL-3.0-only
pragma ComponentBehavior: Bound
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Window
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
@@ -59,84 +56,18 @@ 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
onHeightChanged: {
// HACK: See TimelineView for the hack details.
// We get the height change here *first* so we are informed this is because of a window resize and not due to the pinned message.
(timelineViewLoader.item as TimelineView).resetViewSettling();
}
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
visible: Kirigami.Settings.isMobile || !(root.Kirigami.PageStack.pageStack as Kirigami.PageRow).wideMode
icon.name: "view-right-new"
onTriggered: (root.QQC2.ApplicationWindow.window as Main).openRoomDrawer()
}
]
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: {
@@ -146,9 +77,9 @@ Kirigami.Page {
if (root.currentRoom.tagNames.includes("m.server_notice")) {
banner.text = i18nc("@info", "This room contains official messages from your homeserver.")
banner.show("message");
banner.visible = true;
} else {
banner.hideIf("message");
banner.visible = false;
}
}
@@ -157,110 +88,33 @@ Kirigami.Page {
function onIsOnlineChanged() {
if (!root.currentRoom.connection.isOnline) {
banner.text = i18nc("@info:status", "NeoChat is offline. Please check your network connection.");
banner.visible = true;
banner.type = Kirigami.MessageType.Error;
banner.show("offline");
} else {
banner.hideIf("offline");
banner.visible = false;
}
}
}
header: ColumnLayout {
id: headerLayout
header: Kirigami.InlineMessage {
id: banner
spacing: 0
readonly property bool shouldShowPins: root.currentRoom.pinnedMessage.length > 0 && !Kirigami.Settings.isMobile
QQC2.Control {
id: pinControl
visible: headerLayout.shouldShowPins
Layout.fillWidth: true
background: Rectangle {
color: Kirigami.Theme.backgroundColor
Kirigami.Theme.colorSet: Kirigami.Theme.View
Kirigami.Theme.inherit: false
}
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
Kirigami.Icon {
source: "pin-symbolic"
Layout.preferredWidth: Kirigami.Units.iconSizes.smallMedium
Layout.preferredHeight: Kirigami.Units.iconSizes.smallMedium
}
QQC2.Label {
text: root.currentRoom.pinnedMessage
maximumLineCount: 1
elide: Text.ElideRight
onLinkActivated: link => UrlHelper.openUrl(link)
onHoveredLinkChanged: if (hoveredLink.length > 0 && hoveredLink !== "1") {
(QQC2.ApplicationWindow.window as Main).hoverLinkIndicator.text = hoveredLink;
} else {
(QQC2.ApplicationWindow.window as Main).hoverLinkIndicator.text = "";
}
Layout.fillWidth: true
}
}
TapHandler {
onTapped: pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomPinnedMessagesPage'), {
room: root.currentRoom
}, {
title: i18nc("@title", "Pinned Messages")
});
}
}
Kirigami.Separator {
visible: headerLayout.shouldShowPins
Layout.fillWidth: true
}
Kirigami.InlineMessage {
id: banner
// 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
function show(msgid: string): void {
messageId = msgid;
visible = true;
}
function hideIf(msgid: string): void {
if (messageId == msgid) {
visible = false;
}
}
}
showCloseButton: true
visible: false
position: Kirigami.InlineMessage.Position.Header
}
Loader {
id: timelineViewLoader
anchors.fill: parent
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
active: root.currentRoom && !root.currentRoom.isInvite && !root.currentRoom.isSpace
// We need the loader to be active but invisible while the room is loading messages so signals in TimelineView work.
visible: !root.loading
sourceComponent: TimelineView {
id: timelineView
messageFilterModel: root.messageFilterModel
compactLayout: NeoChatConfig.compactLayout
fileDropEnabled: !Controller.isFlatpak
markReadCondition: NeoChatConfig.markReadCondition
}
}
@@ -293,15 +147,21 @@ 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
height: active ? item.implicitHeight : 0
active: timelineViewLoader.active && !root.currentRoom.readOnly
sourceComponent: ChatBar {
id: chatBar
width: parent.width
currentRoom: root.currentRoom
connection: root.currentRoom.connection as NeoChatConnection
connection: root.currentRoom.connection
}
}
@@ -343,7 +203,7 @@ Kirigami.Page {
function onShowMessage(messageType, message) {
banner.text = message;
banner.type = messageType;
banner.show("generic");
banner.visible = true;
}
function onShowEventSource(eventId) {
@@ -355,18 +215,16 @@ Kirigami.Page {
});
}
function onShowDelegateMenu(parent: QtObject, room: NeoChatRoom, eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, selectedText: string, hoveredLink: string) {
(delegateContextMenu.createObject(parent, {
room: room,
function onShowDelegateMenu(eventId: string, author, messageComponentType, plainText: string, richText: string, mimeType: string, progressInfo, isThread: bool, selectedText: string, hoveredLink: string) {
const contextMenu = delegateContextMenu.createObject(root, {
author: author,
eventId: eventId,
plainText: plainText,
mimeType: mimeType,
progressInfo: progressInfo,
messageComponentType: messageComponentType,
selectedText,
hoveredLink,
}) as DelegateContextMenu).popup();
});
contextMenu.popup();
}
function onShowMaximizedMedia(index) {
@@ -381,12 +239,12 @@ Kirigami.Page {
}
function onShowMaximizedCode(author, time, codeText, language) {
(Qt.createComponent('org.kde.neochat', 'CodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
let popup = Qt.createComponent('org.kde.neochat', 'CodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
author: author,
time: time,
codeText: codeText,
language: language
}) as CodeMaximizeComponent).open();
}).open();
}
}
@@ -394,7 +252,7 @@ Kirigami.Page {
id: delegateContextMenu
DelegateContextMenu {
room: root.currentRoom
connection: root.currentRoom.connection as NeoChatConnection
connection: root.currentRoom.connection
}
}

View File

@@ -1,90 +0,0 @@
// SPDX-FileCopyrightText: 2026 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.components as KirigamiComponents
import org.kde.kirigamiaddons.delegates as Delegates
import org.kde.neochat
Kirigami.Dialog {
id: root
property var model
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
maximumHeight: Kirigami.Units.gridUnit * 24
title: i18nc("@title:menu Seen by/read marker dialog", "Seen By")
contentItem: ColumnLayout {
spacing: 0
QQC2.ScrollView {
Layout.fillWidth: true
Layout.fillHeight: true
ListView {
id: listView
model: root.model
spacing: Kirigami.Units.smallSpacing
onCountChanged: {
if (listView.count === 0) {
root.close();
}
}
delegate: Delegates.RoundedItemDelegate {
id: userDelegate
required property string displayName
required property url avatarUrl
required property color memberColor
required property string userId
implicitHeight: Kirigami.Units.gridUnit * 2
text: displayName
highlighted: false
onClicked: {
root.close();
RoomManager.resolveResource(userDelegate.userId);
}
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
KirigamiComponents.Avatar {
implicitWidth: height
sourceSize {
height: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
width: Kirigami.Units.gridUnit + Kirigami.Units.smallSpacing * 2.5
}
source: userDelegate.avatarUrl
name: userDelegate.displayName
color: userDelegate.memberColor
Layout.fillHeight: true
}
QQC2.Label {
text: userDelegate.displayName
textFormat: Text.PlainText
elide: Text.ElideRight
clip: true // Intentional to limit insane Unicode in display names
Layout.fillWidth: true
}
}
}
}
}
}
}

View File

@@ -1,121 +0,0 @@
// SPDX-FileCopyrightText: 2026 Joshua Goins <josh@redstrate.com>
// SPDX-License-Identifier: GPL-3.0-only
import QtQuick
import QtQuick.Controls as QQC2
import QtQuick.Layouts
import org.kde.kirigami as Kirigami
import org.kde.kirigamiaddons.formcard as FormCard
import org.kde.neochat
Kirigami.Dialog {
id: root
required property NeoChatConnection connection
readonly property SupportController supportController: SupportController {
connection: root.connection
}
readonly property bool hasSupportResources: supportController.supportPage.length > 0 && supportController.contacts.length > 0
title: i18nc("@title Support information", "Support")
width: Math.min(Kirigami.Units.gridUnit * 30, QQC2.ApplicationWindow.window.width)
ColumnLayout {
spacing: 0
FormCard.FormTextDelegate {
id: explanationTextDelegate
text: root.hasSupportResources ?
i18nc("@info:label %1 is the domain of the server", "Official support resources provided by %1:", root.connection.domain)
: i18nc("@info:label %1 is the domain of the server", "%1 has no support resources.", root.connection.domain)
}
FormCard.FormDelegateSeparator {
above: explanationTextDelegate
below: openSupportPageDelegate
visible: openSupportPageDelegate.visible
}
FormCard.FormLinkDelegate {
id: openSupportPageDelegate
icon.name: "help-contents-symbolic"
text: i18nc("@action:button Open support webpage", "Open Support")
url: root.supportController.supportPage
visible: root.supportController.supportPage.length > 0
}
FormCard.FormDelegateSeparator {
above: openSupportPageDelegate
visible: root.supportController.contacts.length > 0
}
Repeater {
model: root.supportController.contacts
delegate: FormCard.AbstractFormDelegate {
id: contactDelegate
required property string role
required property string matrixId
required property string emailAddress
background: null
Layout.fillWidth: true
contentItem: RowLayout {
spacing: Kirigami.Units.largeSpacing
Kirigami.Icon {
source: "user"
}
QQC2.Label {
text: {
// Translate known keys
if (contactDelegate.role === "m.role.admin") {
return i18nc("@info:label Adminstrator contact", "Admin")
} else if (contactDelegate.role === "m.role.security") {
return i18nc("@info:label Security contact", "Security")
}
return contactDelegate.role;
}
elide: Text.ElideRight
Layout.fillWidth: true
}
QQC2.ToolButton {
visible: contactDelegate.matrixId.length > 0
icon.name: "document-send-symbolic"
onClicked: {
root.close();
root.connection.requestDirectChat(contactDelegate.matrixId);
}
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: i18nc("@info:tooltip %1 is a Matrix ID", "Contact via Matrix (%1)", contactDelegate.matrixId)
}
QQC2.ToolButton {
visible: contactDelegate.emailAddress.length > 0
icon.name: "mail-sent-symbolic"
onClicked: Qt.openUrlExternally("mailto:%1".arg(contactDelegate.emailAddress))
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: i18nc("@info:tooltip %1 is an e-mail address", "Contact via e-mail (%1)", contactDelegate.emailAddress)
}
}
}
}
}
}

View File

@@ -11,9 +11,7 @@ import org.kde.neochat
FormCard.FormCardPage {
id: root
property bool processing: false
title: i18nc("@title:window", "Manage Secret Backup")
title: i18nc("@title:window", "Load your encrypted messages")
topPadding: Kirigami.Units.gridUnit
leftPadding: 0
@@ -27,42 +25,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 +101,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 +120,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

@@ -20,47 +20,29 @@ Kirigami.Dialog {
// Make sure that code is prepared to deal with this property being null
property NeoChatRoom room
property var user
// Used for the "View Main Profile" feature so you can toggle back to the room profile.
property NeoChatRoom oldRoom
property NeoChatConnection connection
property CommonRoomsModel model: CommonRoomsModel {
connection: root.connection
userId: root.user.id
}
property LimiterModel limiterModel: LimiterModel {
maximumCount: 5
sourceModel: root.model
}
readonly property bool isSelf: root.user.id === root.connection.localUserId
readonly property bool hasMutualRooms: root.model.count > 0
readonly property bool isRoomProfile: root.room
readonly property string shareUrl: "https://matrix.to/#/" + root.user.id
leftPadding: Kirigami.Units.largeSpacing * 2
rightPadding: Kirigami.Units.largeSpacing * 2
topPadding: Kirigami.Units.largeSpacing * 2
bottomPadding: Kirigami.Units.largeSpacing * 2
leftPadding: 0
rightPadding: 0
topPadding: 0
bottomPadding: 0
standardButtons: Kirigami.Dialog.NoButton
width: Math.min(QQC2.ApplicationWindow.window.width, Kirigami.Units.gridUnit * 24)
title: i18nc("@title:menu Account details dialog", "Account Details")
header: null
contentItem: ColumnLayout {
spacing: 0
RowLayout {
id: detailRow
spacing: Kirigami.Units.largeSpacing
Layout.fillWidth: true
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.rightMargin: Kirigami.Units.largeSpacing
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
spacing: Kirigami.Units.largeSpacing
KirigamiComponents.Avatar {
id: avatar
@@ -68,14 +50,7 @@ Kirigami.Dialog {
Layout.preferredHeight: Kirigami.Units.iconSizes.huge
name: root.room ? root.room.member(root.user.id).displayName : root.user.displayName
source: {
if (root.room) {
return root.room.member(root.user.id).avatarUrl;
} else if(root.user.avatarUrl.toString() !== '') {
return root.connection.makeMediaUrl(root.user.avatarUrl);
}
return "";
}
source: root.room ? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
color: root.room ? root.room.member(root.user.id).color : QmlUtils.getUserColor(root.user.hueF)
}
@@ -100,363 +75,224 @@ Kirigami.Dialog {
id: idLabel
textFormat: TextEdit.PlainText
text: idLabelTextMetrics.elidedText
color: Kirigami.Theme.disabledTextColor
TextMetrics {
id: idLabelTextMetrics
text: root.user.id
elide: Qt.ElideRight
elideWidth: root.availableWidth - avatar.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
elideWidth: root.availableWidth - avatar.width - qrButton.width - detailRow.spacing * 2 - detailRow.Layout.leftMargin - detailRow.Layout.rightMargin
}
}
Kirigami.ActionToolBar {
QQC2.Label {
property CommonRoomsModel model: CommonRoomsModel {
connection: root.connection
userId: root.user.id
}
text: i18ncp("@info", "One mutual room", "%1 mutual rooms", model.count)
color: Kirigami.Theme.disabledTextColor
visible: model.count > 0
Layout.topMargin: Kirigami.Units.smallSpacing
actions: [
Kirigami.Action {
text: i18nc("@action:intoolbar Message this user directly", "Message")
icon.name: "document-send-symbolic"
onTriggered: {
root.close();
root.connection.requestDirectChat(root.user.id);
}
},
Kirigami.Action {
icon.name: "im-invisible-user-symbolic"
text: root.connection.isIgnored(root.user.id) ? i18nc("@action:intoolbar Unignore or 'unblock' this user", "Unignore") : i18nc("@action:intoolbar Ignore or 'block' this user", "Ignore")
onTriggered: {
root.close();
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
}
},
Kirigami.Action {
text: i18nc("@action:intoolbar Copy shareable link for this user", "Copy Link")
icon.name: "username-copy-symbolic"
onTriggered: Clipboard.saveText(root.shareUrl)
},
Kirigami.Action {
text: i18nc("@action:intoolbar Search for this user's messages.", "Search Messages…")
icon.name: "search-symbolic"
onTriggered: {
((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
room: root.room,
senderId: root.user.id
}, {
title: i18nc("@action:title", "Search")
});
root.close();
}
},
Kirigami.Action {
text: i18nc("@action:intoolbar", "Show QR Code")
icon.name: "view-barcode-qr-symbolic"
onTriggered: {
let qrCode = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
text: root.shareUrl,
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName,
subtitle: root.user.id,
avatarColor: root.room?.member(root.user.id).color,
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
}) as QrCodeMaximizeComponent;
root.close();
qrCode.open();
}
},
Kirigami.Action {
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")
onTriggered: {
let dialog = ((root.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", "Optionally give a reason for reporting this user"),
icon: "dialog-warning-symbolic",
actionText: i18nc("@action:button 'Report' as in 'Report this user to the administrators'", "Report"),
reporting: true,
connection: root.connection,
}, {
title: i18nc("@title", "Report User"),
width: Kirigami.Units.gridUnit * 25
}) as ReasonDialog;
dialog.accepted.connect(reason => {
root.connection.reportUser(root.user.id, reason);
});
}
},
Kirigami.Action {
visible: root.room
text: i18nc("@action:button", "View Main Profile")
icon.name: "user-properties-symbolic"
onTriggered: {
root.oldRoom = root.room;
root.room = null;
}
},
Kirigami.Action {
visible: !root.room && root.oldRoom
text: i18nc("@action:button", "View Room Profile")
icon.name: "user-properties-symbolic"
onTriggered: {
root.room = root.oldRoom;
root.oldRoom = null;
}
}
]
}
}
}
QQC2.AbstractButton {
id: qrButton
Layout.minimumHeight: avatar.height * 0.75
Layout.maximumHeight: avatar.height * 1.5
Layout.maximumWidth: avatar.height * 1.5
Kirigami.Heading {
text: i18nc("@title Moderation actions for this user", "Moderation")
level: 2
visible: root.isRoomProfile && moderationToolbar.actions.filter(function (it) { return it.visible; }).length > 0
Layout.topMargin: Kirigami.Units.largeSpacing
}
Kirigami.ActionToolBar {
id: moderationToolbar
flat: false
visible: root.isRoomProfile
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
actions: [
Kirigami.Action {
visible: {
if (root.room) {
return !root.isSelf && root.room.canSendState("kick") && root.room.containsUser(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId);
}
return false;
}
text: i18nc("@action:button Kick the user from the room", "Kick…")
icon.name: "im-kick-user"
onTriggered: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Kick User"),
placeholder: i18nc("@info:placeholder", "Optionally give a reason for kicking this user"),
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
icon: "im-kick-user",
reporting: false,
connection: root.connection,
}, {
title: i18nc("@title:dialog", "Kick User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.kickMember(root.user.id, reason);
});
root.close();
}
},
Kirigami.Action {
visible: {
if (root.room) {
return !root.isSelf && root.room.canSendState("ban") && !root.room.isUserBanned(root.user.id) && root.room.memberEffectivePowerLevel(root.user.id) < root.room.memberEffectivePowerLevel(root.connection.localUserId);
}
return false;
}
text: i18nc("@action:button Ban this user from the room", "Ban…")
icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Ban User"),
placeholder: i18nc("@info:placeholder", "Optionally give a reason for banning this user"),
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
icon: "im-ban-user",
reporting: false,
connection: root.connection,
}, {
title: i18nc("@title:dialog", "Ban User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.ban(root.user.id, reason);
});
root.close();
}
},
Kirigami.Action {
visible: {
if (root.room) {
return !root.isSelf && root.room.canSendState("ban") && root.room.isUserBanned(root.user.id);
}
return false;
}
text: i18nc("@action:button Unban the user from this room", "Unban")
icon.name: "im-irc"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
root.room.unban(root.user.id);
root.close();
}
},
Kirigami.Action {
visible: (root.user.id === root.connection.localUserId || (root.room?.canSendState("redact") ?? false))
text: i18nc("@action:button Remove messages from the user in this room", "Remove Messages…")
icon.name: "delete"
icon.color: Kirigami.Theme.negativeTextColor
onTriggered: {
let dialog = ((root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack as Kirigami.PageRow).pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Remove Messages"),
placeholder: i18nc("@info:placeholder", "Optionally give a reason for removing this user's recent messages"),
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
icon: "delete",
reporting: false,
connection: root.connection,
}, {
title: i18nc("@title", "Remove Messages"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.deleteMessagesByUser(root.user.id, reason);
});
root.close();
}
contentItem: Barcode {
id: barcode
barcodeType: Barcode.QRCode
content: "https://matrix.to/#/" + root.user.id
}
]
}
Kirigami.Heading {
text: i18nc("@title Role such as 'Admin' or 'Moderator' for this user", "Power Level")
level: 2
visible: root.isRoomProfile
Layout.topMargin: Kirigami.Units.largeSpacing
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
visible: root.isRoomProfile
Layout.topMargin: Kirigami.Units.smallSpacing
QQC2.Label {
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : ""
}
QQC2.Button {
visible: {
if (root.room) {
return root.room.canSendState("m.room.power_levels") && !(root.room.roomCreatorHasUltimatePowerLevel() && root.room.isCreator(root.user.id));
}
return false;
}
text: i18nc("@action:button Set the power level (such as 'Admin') for this user", "Set Power Level")
icon.name: "document-edit-symbolic"
display: QQC2.AbstractButton.IconOnly
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: text
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
onClicked: {
(powerLevelDialog.createObject(this, {
room: root.room,
userId: root.user.id,
powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
}) as PowerLevelDialog).open();
let map = Qt.createComponent('org.kde.neochat', 'QrCodeMaximizeComponent').createObject(QQC2.Overlay.overlay, {
text: barcode.content,
title: root.room ? root.room.member(root.user.id).displayName : root.user.displayName,
subtitle: root.user.id,
avatarColor: root.room?.member(root.user.id).color,
avatarSource: root.room? root.room.member(root.user.id).avatarUrl : root.user.avatarUrl
});
root.close();
map.open();
}
Component {
id: powerLevelDialog
PowerLevelDialog {
id: powerLevelDialog
}
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: barcode.content
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
Kirigami.Heading {
text: i18nc("@title The set of common rooms between your current user and the one shown", "Mutual Rooms")
level: 4
visible: !root.isSelf && root.hasMutualRooms
Kirigami.Chip {
visible: root.room
text: root.room ? QmlUtils.nameForPowerLevelValue(root.room.memberEffectivePowerLevel(root.user.id)) : ""
closable: false
checkable: false
Layout.topMargin: Kirigami.Units.largeSpacing
Layout.leftMargin: Kirigami.Units.largeSpacing
Layout.bottomMargin: Kirigami.Units.largeSpacing
}
RowLayout {
spacing: Kirigami.Units.smallSpacing
visible: !root.isSelf && root.hasMutualRooms
Layout.topMargin: Kirigami.Units.smallSpacing
Repeater {
model: root.limiterModel
delegate: KirigamiComponents.AvatarButton {
required property string roomName
required property string roomAvatar
required property string roomId
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
name: roomName
source: roomAvatar
onClicked: {
root.close();
RoomManager.resolveResource(roomId);
}
QQC2.ToolTip.visible: hovered
QQC2.ToolTip.text: name
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
}
}
QQC2.Label {
text: i18ncp("@info:label And '%1' more rooms you have in common with this user, but are not shown", "and 1 more…", "and %1 more…", root.limiterModel.extraCount)
visible: root.limiterModel.extraCount > 0
color: Kirigami.Theme.disabledTextColor
}
}
Kirigami.Heading {
text: i18nc("@title Private note for this user", "Private Note")
level: 4
Layout.topMargin: Kirigami.Units.largeSpacing
}
QQC2.TextArea {
id: noteText
text: root.connection.noteForUser(root.user.id)
textFormat: TextEdit.PlainText
wrapMode: TextEdit.Wrap
placeholderText: i18nc("@info:placeholder", "Only visible to you")
onTextEdited: editTimer.restart()
Kirigami.Separator {
Layout.fillWidth: true
Layout.topMargin: Kirigami.Units.smallSpacing
}
// Prevent unnecessary edits by waiting 1 second
Timer {
id: editTimer
interval: 1000
onTriggered: root.connection.setNoteForUser(root.user.id, noteText.text)
FormCard.FormButtonDelegate {
visible: root.user.id !== root.connection.localUserId && !!root.user
text: !!root.user && root.connection.isIgnored(root.user.id) ? i18n("Unignore this user") : i18n("Ignore this user")
icon.name: "im-invisible-user"
onClicked: {
root.close();
root.connection.isIgnored(root.user.id) ? root.connection.removeFromIgnoredUsers(root.user.id) : root.connection.addToIgnoredUsers(root.user.id);
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("kick") && room.containsUser(root.user.id) && room.memberEffectivePowerLevel(root.user.id) < room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button", "Kick this user")
icon.name: "im-kick-user"
onClicked: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Kick User"),
placeholder: i18nc("@info:placeholder", "Reason for kicking this user"),
actionText: i18nc("@action:button 'Kick' as in 'Kick this user from the room'", "Kick"),
icon: "im-kick-user"
}, {
title: i18nc("@title:dialog", "Kick User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.kickMember(root.user.id, reason);
});
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("invite") && !room.containsUser(root.user.id)
enabled: root.room && !root.room.isUserBanned(root.user.id)
text: i18nc("@action:button", "Invite this user")
icon.name: "list-add-user"
onClicked: {
root.room.inviteToRoom(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("ban") && !room.isUserBanned(root.user.id) && room.memberEffectivePowerLevel(root.user.id) < room.memberEffectivePowerLevel(root.connection.localUserId)
text: i18nc("@action:button", "Ban this user")
icon.name: "im-ban-user"
icon.color: Kirigami.Theme.negativeTextColor
onClicked: {
let dialog = (root.QQC2.ApplicationWindow.window as Kirigami.ApplicationWindow).pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Ban User"),
placeholder: i18nc("@info:placeholder", "Reason for banning this user"),
actionText: i18nc("@action:button 'Ban' as in 'Ban this user'", "Ban"),
icon: "im-ban-user"
}, {
title: i18nc("@title:dialog", "Ban User"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.ban(root.user.id, reason);
});
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.user.id !== root.connection.localUserId && room.canSendState("ban") && room.isUserBanned(root.user.id)
text: i18nc("@action:button", "Unban this user")
icon.name: "im-irc"
icon.color: Kirigami.Theme.negativeTextColor
onClicked: {
root.room.unban(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.room && root.room.canSendState("m.room.power_levels")
text: i18nc("@action:button", "Set user power level")
icon.name: "visibility"
onClicked: {
let dialog = powerLevelDialog.createObject(this, {
room: root.room,
userId: root.user.id,
powerLevel: root.room.memberEffectivePowerLevel(root.user.id)
});
dialog.open();
root.close();
}
Component {
id: powerLevelDialog
PowerLevelDialog {
id: powerLevelDialog
}
}
}
FormCard.FormButtonDelegate {
visible: root.room && (root.user.id === root.connection.localUserId || room.canSendState("redact"))
text: i18nc("@action:button", "Remove recent messages by this user")
icon.name: "delete"
icon.color: Kirigami.Theme.negativeTextColor
onClicked: {
let dialog = pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'ReasonDialog'), {
title: i18nc("@title:dialog", "Remove Messages"),
placeholder: i18nc("@info:placeholder", "Reason for removing this user's recent messages"),
actionText: i18nc("@action:button 'Remove' as in 'Remove these messages'", "Remove"),
icon: "delete"
}, {
title: i18nc("@title", "Remove Messages"),
width: Kirigami.Units.gridUnit * 25
});
dialog.accepted.connect(reason => {
root.room.deleteMessagesByUser(root.user.id, reason);
});
root.close();
}
}
FormCard.FormButtonDelegate {
visible: root.user.id !== root.connection.localUserId
text: root.connection.directChatExists(root.user) ? i18nc("%1 is the name of the user.", "Chat with %1", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName)) : i18n("Invite to private chat")
icon.name: "document-send"
onClicked: {
root.connection.requestDirectChat(root.user.id);
root.close();
}
}
FormCard.FormButtonDelegate {
text: i18nc("@action:button %1 is the name of the user.", "Search room for %1's messages", root.room ? root.room.member(root.user.id).htmlSafeDisplayName : QmlUtils.escapeString(root.user.displayName))
icon.name: "search-symbolic"
onClicked: {
pageStack.pushDialogLayer(Qt.createComponent('org.kde.neochat', 'RoomSearchPage'), {
room: root.room,
senderId: root.user.id
}, {
title: i18nc("@action:title", "Search")
});
root.close();
}
}
FormCard.FormButtonDelegate {
text: i18n("Copy link")
icon.name: "username-copy"
onClicked: Clipboard.saveText("https://matrix.to/#/" + root.user.id)
}
}
}

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()
@@ -53,16 +53,6 @@ SearchPage {
root.closeDialog();
}
QQC2.ContextMenu.menu: QQC2.Menu {
QQC2.MenuItem {
text: i18nc("@action:inmenu", "Copy User ID")
icon.name: "username-copy"
onTriggered: {
Clipboard.saveText(userDelegate.userId)
}
}
}
contentItem: RowLayout {
spacing: Kirigami.Units.smallSpacing
@@ -81,7 +71,7 @@ SearchPage {
}
QQC2.Label {
visible: userDelegate.directChatExists
text: i18nc("@info", "Direct Messages")
text: i18nc("@info", "Friends")
textFormat: Text.PlainText
color: Kirigami.Theme.positiveTextColor
}

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