Compare commits
36 Commits
release/24
...
work/nvrwh
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e8b269869c | ||
|
|
7fd8394253 | ||
|
|
c54a447caf | ||
|
|
61b009422d | ||
|
|
5a8b0184ea | ||
|
|
f48c2a21d9 | ||
|
|
538cfbee8d | ||
|
|
7666f1c362 | ||
|
|
4b5d828bf8 | ||
|
|
4bd160cceb | ||
|
|
5f56fc1156 | ||
|
|
72a2a74395 | ||
|
|
f6a5cc7c25 | ||
|
|
80f3bd64b6 | ||
|
|
1f69a96766 | ||
|
|
f963e06983 | ||
|
|
e6980e2370 | ||
|
|
8e8105d04d | ||
|
|
21d9e69712 | ||
|
|
e0783a3c6e | ||
|
|
3b9337d2a8 | ||
|
|
85cda8ffa7 | ||
|
|
f1efc1f17d | ||
|
|
0486fa61cd | ||
|
|
2247a2a7af | ||
|
|
08a0fbfd6b | ||
|
|
898b993b94 | ||
|
|
77e366b179 | ||
|
|
981edc9cf7 | ||
|
|
d45aa14348 | ||
|
|
4926488d49 | ||
|
|
dcc1935150 | ||
|
|
55364a8eb8 | ||
|
|
70bb06715f | ||
|
|
ec4aa73e37 | ||
|
|
c1d122a717 |
@@ -2,4 +2,6 @@
|
||||
; SPDX-License-Identifier: CC0-1.0
|
||||
|
||||
[BlueprintSettings]
|
||||
kde/applications/neochat.packageAppx = True
|
||||
kde/frameworks/extra-cmake-modules.version=master
|
||||
kde/unreleased/kirigami-addons.version=master
|
||||
libs/qt.qtMajorVersion=6
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"id": "org.kde.neochat",
|
||||
"branch": "master",
|
||||
"runtime": "org.kde.Platform",
|
||||
"runtime-version": "6.6",
|
||||
"runtime-version": "6.6-kf6preview",
|
||||
"sdk": "org.kde.Sdk",
|
||||
"command": "neochat",
|
||||
"tags": [
|
||||
|
||||
@@ -8,8 +8,8 @@ cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
# KDE Applications version, managed by release script.
|
||||
set(RELEASE_SERVICE_VERSION_MAJOR "24")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "02")
|
||||
set(RELEASE_SERVICE_VERSION_MICRO "2")
|
||||
set(RELEASE_SERVICE_VERSION_MINOR "04")
|
||||
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})
|
||||
|
||||
@@ -61,7 +61,6 @@ private Q_SLOTS:
|
||||
void genericBody_data();
|
||||
void genericBody();
|
||||
void nullGenericBody();
|
||||
void markdownBody();
|
||||
void subtitle();
|
||||
void nullSubtitle();
|
||||
void mediaInfo();
|
||||
@@ -347,13 +346,6 @@ void EventHandlerTest::nullGenericBody()
|
||||
QCOMPARE(noEventHandler.getGenericBody(), QString());
|
||||
}
|
||||
|
||||
void EventHandlerTest::markdownBody()
|
||||
{
|
||||
eventHandler.setEvent(room->messageEvents().at(0).get());
|
||||
|
||||
QCOMPARE(eventHandler.getMarkdownBody(), QStringLiteral("This is an example\ntext message"));
|
||||
}
|
||||
|
||||
void EventHandlerTest::subtitle()
|
||||
{
|
||||
auto event = room->messageEvents().at(0).get();
|
||||
|
||||
@@ -59,8 +59,7 @@
|
||||
<summary xml:lang="fi">Keskustelu ystäviesi kanssa Matrixissa</summary>
|
||||
<summary xml:lang="fr">Discuter avec vos ami(e)s sur le réseau Matrix</summary>
|
||||
<summary xml:lang="gl">Charle coas súas amizades en Matrix.</summary>
|
||||
<summary xml:lang="hu">Csevegjen barátaival a matrixon</summary>
|
||||
<summary xml:lang="ia">Starta Conversation con tu amicos sur matrix</summary>
|
||||
<summary xml:lang="ia">Starta Conversation conntu amicos sur matrix</summary>
|
||||
<summary xml:lang="it">Conversa con i tuoi contatti su matrix</summary>
|
||||
<summary xml:lang="ka">ესაუბრეთ მეგობრებს Matrix-ზე</summary>
|
||||
<summary xml:lang="ko">Matrix를 사용하여 친구들과 대화하기</summary>
|
||||
@@ -70,7 +69,7 @@
|
||||
<summary xml:lang="sl">Klepet z vašimi prijatelji na matrixu</summary>
|
||||
<summary xml:lang="sv">Chatta med dina vänner på Matrix</summary>
|
||||
<summary xml:lang="ta">மேட்ரிக்ஸு மூலம் உங்கள் நண்பர்களிடம் பேசலாம்</summary>
|
||||
<summary xml:lang="tr">Matrix’te arkadaşlarınızla sohbet edin</summary>
|
||||
<summary xml:lang="tr">Matrix'te arkadaşlarınızla sohbet edin</summary>
|
||||
<summary xml:lang="uk">Спілкуйтеся з вашими друзями у matrix</summary>
|
||||
<summary xml:lang="x-test">xxChat with your friends on matrixxx</summary>
|
||||
<summary xml:lang="zh-CN">在 Matrix 上与朋友聊天</summary>
|
||||
@@ -88,7 +87,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="fi">NeoChat on asiakassovellus Matrixille, hajautetulle pikaviestinyhteyskäytännölle. Sillä voi lähettää teksti-, video- ja ääniviestejä perheelle, tutuille ja ystäville. Se käyttää KDE-kehystä ja erityisesti Kirigamia tuottaakseen mukautuvan monialustaisen käyttökokemuksen.</p>
|
||||
<p xml:lang="fr">NeoChat est un client pour le protocole Matrix, un protocole décentralisé de communications pour messagerie instantané. Il vous permet d'envoyer des messages de texte, des vidéos et des fichiers audio à votre famille, vos collègues et vos amis. Il utilise les environnements de développement et plus précisément Kirigami pour fournir une expérience convergente sur plusieurs plate-formes. </p>
|
||||
<p xml:lang="gl">NeoChat é un cliente para Matrix, o protocolo de comunicación descentralizada para mensaxaría instantánea. Podes enviar mensaxes de texto, vídeos e ficheiros de son á túa familia, colegas e amizades. Usas infraestruturas de KDE e principalmente Kirigami para proporcionar unha experiencia de uso converxente para varias plataformas.</p>
|
||||
<p xml:lang="hu">A NeoChat egy kliens a Matrixhoz, az azonnali üzenetküldés decentralizált komunikációs protokolljához. Szöveges üzeneteket, videókat és hangfájlokat küldhet családjának, kollégáinak és barátainak. A KDE keretrendszert használja, a Kirigaminak köszönhetően konvergens élményt nyújt több platformon is.</p>
|
||||
<p xml:lang="ia">NeoChat es un cliente per Matrix, le protocollo de communication decentralisate per messager instantanee. Illo te permitte inviar messager de texto, files de video e audio a tu familia, collegas e amicos usante. Illo usa KDE frameworks e super toto Kirigamii forni un experientia convergente trans platteforme multiple.</p>
|
||||
<p xml:lang="it">NeoChat è un client per Matrix, il protocollo di comunicazione decentralizzato per la messaggistica istantanea. Ti consente di inviare messaggi di testo, video e file audio a familiari, colleghi e amici. Utilizza i framework KDE e in particolare Kirigami per fornire un'esperienza convergente su più piattaforme.</p>
|
||||
<p xml:lang="ka">NeoChat არის Matrix კლიენტი. ის საშუალებას გაძლევთ გაგზავნოთ ტექსტური შეტყობინებები, ვიდეოები და აუდიო ფაილები თქვენს ოჯახს, კოლეგებსა და მეგობრებს მატრიქსის პროტოკოლის გამოყენებით.</p>
|
||||
@@ -114,7 +112,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="fi">NeoChat pyrkii olemaan Matrix-määritelmän täysominaisuuksinen sovellus, joten se tukee kaikkea nykyisessä vakaassa määritelmässä muutamaa huomattavaa poikkeusta lukuun ottamatta (VoIP, säikeet ja jotkin piirteet päästä päähän -salauksessa). Joitakin pienempiäkin puutteita on Matrix-määritelmän jatkuvan kehityksen vuoksi, mutta lopputavoitteena on tarjota määritelmän täysi tuki.</p>
|
||||
<p xml:lang="fr">L'objectif de NeoChat est d'être une application complète pour le protocole Matrix. En tant que tel, tout dans la spécification stable actuelle avec les exceptions notables de VoIP, les processus et certains aspects du chiffrement de bout en bout sont pris en charge. Il y a quelques autres petites omissions en raison du fait que la spécification du protocole Matrix est en constante évolution. Cependant, l'objectif reste de fournir un soutien éventuel pour l'ensemble de la spécification.</p>
|
||||
<p xml:lang="gl">NeoChat pretende ser unha aplicación completa para a especificación de Matrix. Coas excepcións de VoIP, conversas fiadas e algúns aspectos da cifraxe de extremo a extremo, a versión estábel segue as especificacións. Existen algunhas outras pequenas omisións debido ao feito de que Matrix está en continua evolución pero a intención é implementar a especificación completa.</p>
|
||||
<p xml:lang="hu">A NeoChat célja, hogy a Matrix specifikációnak megfelelő teljes funkcionalitású alkalmazás legyen. Mint ilyen, a jelenlegi stabil specifikáció támogatott a VoIP, a szálak és a végpontok közötti titkosítás egyes elemeinek kivételével. Van még néhány kisebb hiányosság annak köszönhetően, hogy a Matrix specifikáció folyamatosan fejlődik, de végső cél a teljes specifikáció megvalósítása.</p>
|
||||
<p xml:lang="ia">NeoChat aspira a esser un application plenemente eminente per le specification de Matrix. Tal como omne cosas in le specification currentemente stabile con le exceptiones notabile de VOIP, threads e alcun aspectos del cryptation End-to-End es supportate. Il ha ltere pauc omissiones, debite al facto que le specification de Matrix es in evolution constante ma le aspiration remane a fornir supporto eventual per le integre specification.</p>
|
||||
<p xml:lang="it">NeoChat mira ad essere un'applicazione completa per le specifiche Matrix. Pertanto, sono supportati tutti gli elementi dell'attuale specifica stabile con le notevoli eccezioni di VoIP, conversazioni e alcuni aspetti della cifratura end-to-end. Ci sono alcune altre piccole omissioni dovute al fatto che le specifiche Matrix sono in continua evoluzione, ma l'obiettivo rimane quello di fornire un eventuale supporto per l'intera specifica.</p>
|
||||
<p xml:lang="ka">NeoChat-ი მიზნად ისახავს Matrix სპეციფიკაციის სრული განხორციელება ჰქონდეს. როგორც ასეთი, ყველაფერი მიმდინარე სპეციფიკაციიდან, VoIP-ის, ძაფებისა და გამჭოლი დაშიფვრის ზოგიერთი ასპექტის გარდა, მხარდაჭერილია. შეძლება ასევე იყოს მცირე ლაფსუსებიც იმის გამო, რომ Matrix-ის სპეციფიკაცია მუდმივად ვითარგდება, მაგრამ ჩვენი მიზანი მისი სრული მხარდაჭერაა.</p>
|
||||
@@ -125,7 +122,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="pt">O NeoChat pretende ser uma aplicação completa para a especificação do Matrix. Como tal, tudo o que existe na especificação estável actual, com as notáveis excepções do VoIP, tópicos e alguns aspectos da Encriptação Ponto-a-Ponto, são suportados. Existem mais algumas omissões, devido ao facto que a norma do Matrix está em constante evolução, mas o objectivo continua a ser oferecer o suporte eventual para a norma por inteiro.</p>
|
||||
<p xml:lang="sl">Neochat cilja, da bi bila popolna aplikacija po specifikaciji Matrixa. Kot takšna vsebuje vse v trenutni stabilni specifikaciji z pomembnimi izjemami pri VoIP, nitih in nekaterih vidikov šifriranja od konca do konca. Obstaja nekaj drugih manjših opustitev zaradi dejstva, da se specifikacija Matrix nenehno razvija, vendar cilj ostaja zagotoviti morebitno podporo celotni specifikaciji.</p>
|
||||
<p xml:lang="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 Şifreleme’nin 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="tr">NeoChat, Matrix belirtimi için tam özellikli bir uygulama olmayı hedefler. Bu nedenle; VoIP, ileti zincirleri ve Uçtan Uca Şifreleme'nin 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>
|
||||
@@ -140,7 +137,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<p xml:lang="fi">Matrix-määritelmän kehittyessä NeoChat tukee myös monia epävakaita ominaisuuksia. Tällä hetkellä näitä ovat:</p>
|
||||
<p xml:lang="fr">En raison de la nature du développement des spécifications du protocole Matrix, NeoChat prend également en charge de nombreuses fonctionnalités instables. Actuellement, ce sont :</p>
|
||||
<p xml:lang="gl">Debido á natureza do desenvolvemento da especificación de Matrix, NeoChat tamén inclúe varias funcionalidades non estábeis:</p>
|
||||
<p xml:lang="hu">A Matrix specifikáció fejlesztésének jellegéből adódóan a NeoChat számos instabil funkciót is támogat. Jelenleg a következőket:</p>
|
||||
<p xml:lang="ia">Debite al natura del disveloppamento de specification de Matrix NeoChat tamben supporta numerose characteristicas instabile. Currentemente istes es:</p>
|
||||
<p xml:lang="it">A causa della natura dello sviluppo delle specifiche Matrix, NeoChat supporta anche numerose funzionalità instabili. Attualmente queste sono:</p>
|
||||
<p xml:lang="ka">Matrix-ის სპეციფიკაციის განვითარების ბუნების გამო NeoChat-ს ასევე აქვს უამრავი არასტაბილური ფუნქციაც. ახლა ისინია:</p>
|
||||
@@ -168,7 +164,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="fi">Kyselyt – MSC3381</li>
|
||||
<li xml:lang="fr">Sondages - MSC3381</li>
|
||||
<li xml:lang="gl">Enquisas — MSC3381</li>
|
||||
<li xml:lang="hu">Szavazások - MSC3381</li>
|
||||
<li xml:lang="ia">Inquestas - MSC3381</li>
|
||||
<li xml:lang="it">Sondaggi - MSC3381</li>
|
||||
<li xml:lang="ka">Polls - MSC3381</li>
|
||||
@@ -180,7 +175,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="sl">Polls - MSC3381</li>
|
||||
<li xml:lang="sv">Polls - MSC3381</li>
|
||||
<li xml:lang="ta">வாக்கெடுப்புகள் - MSC3381</li>
|
||||
<li xml:lang="tr">Anketler — 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>
|
||||
@@ -195,7 +190,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="fi">Tarrapakkaukset – MSC2545</li>
|
||||
<li xml:lang="fr">Paquets d'auto-collants - MSC2545</li>
|
||||
<li xml:lang="gl">Paquetes de adhesivos — MSC2545</li>
|
||||
<li xml:lang="hu">Matricacsomagok - MSC2545</li>
|
||||
<li xml:lang="ia">Etiquetta gummate (sticker) -MSC2545</li>
|
||||
<li xml:lang="it">Pacchetti di adesivi - MSC2545</li>
|
||||
<li xml:lang="ka">სტიკერების პაკეტები - MSC2545</li>
|
||||
@@ -207,7 +201,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="sl">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="sv">Sticker Packs - MSC2545</li>
|
||||
<li xml:lang="ta">ஒட்டி தொகுப்புகள் - MSC2545</li>
|
||||
<li xml:lang="tr">Yapışkan Paketleri — MSC2545</li>
|
||||
<li xml:lang="tr">Yapışkan Paketleri - MSC2545</li>
|
||||
<li xml:lang="uk">Пакунки наліпок - MSC2545</li>
|
||||
<li xml:lang="x-test">xxSticker Packs - MSC2545xx</li>
|
||||
<li xml:lang="zh-TW">貼圖包 - MSC2545</li>
|
||||
@@ -222,7 +216,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="fi">Sijaintitapahtumat – MSC3488</li>
|
||||
<li xml:lang="fr">Événements de lieu - MSC3488</li>
|
||||
<li xml:lang="gl">Localización de eventos — MSC3488</li>
|
||||
<li xml:lang="hu">Események helyadatai - MSC3488</li>
|
||||
<li xml:lang="ia">Eventos de Location - MSC3488</li>
|
||||
<li xml:lang="it">Località eventi - MSC3488</li>
|
||||
<li xml:lang="ka">მდებარეობის მოვლენები - MSC3488</li>
|
||||
@@ -234,7 +227,7 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<li xml:lang="sl">Location Events - MSC3488</li>
|
||||
<li xml:lang="sv">Location Events - MSC3488</li>
|
||||
<li xml:lang="ta">இட நிகழ்வுகள் - MSC3488</li>
|
||||
<li xml:lang="tr">Konum Etkinlikleri — 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>
|
||||
@@ -245,44 +238,11 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<categories>
|
||||
<category>Network</category>
|
||||
</categories>
|
||||
<developer_name>The KDE Community</developer_name>
|
||||
<developer_name xml:lang="ar">مجتمع كِيدِي</developer_name>
|
||||
<developer_name xml:lang="az">KDE Cəmiyyəti</developer_name>
|
||||
<developer_name xml:lang="ca">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="ca-valencia">La comunitat KDE</developer_name>
|
||||
<developer_name xml:lang="cs">Komunita KDE</developer_name>
|
||||
<developer_name xml:lang="de">Die KDE-Gemeinschaft</developer_name>
|
||||
<developer_name xml:lang="el">Η Κοινότητα του KDE</developer_name>
|
||||
<developer_name xml:lang="en-GB">The KDE Community</developer_name>
|
||||
<developer_name xml:lang="eo">La KDE-Komunumo</developer_name>
|
||||
<developer_name xml:lang="es">La comunidad KDE</developer_name>
|
||||
<developer_name xml:lang="eu">KDE komunitatea</developer_name>
|
||||
<developer_name xml:lang="fi">KDE-yhteisö</developer_name>
|
||||
<developer_name xml:lang="fr">La communauté de KDE</developer_name>
|
||||
<developer_name xml:lang="gl">A comunidade KDE</developer_name>
|
||||
<developer_name xml:lang="hu">A KDE Közösség</developer_name>
|
||||
<developer_name xml:lang="ia">Le communitate de KDE</developer_name>
|
||||
<developer_name xml:lang="id">Komunitas KDE</developer_name>
|
||||
<developer_name xml:lang="ie">Li comunité de KDE</developer_name>
|
||||
<developer_name xml:lang="it">La comunità KDE</developer_name>
|
||||
<developer_name xml:lang="ka">KDE-ის საზოგადოება</developer_name>
|
||||
<developer_name xml:lang="ko">KDE 커뮤니티</developer_name>
|
||||
<developer_name xml:lang="nl">De KDE gemeenschap</developer_name>
|
||||
<developer_name xml:lang="nn">KDE-fellesskapet</developer_name>
|
||||
<developer_name xml:lang="pa">ਕੇਡੀਈ ਕਮਿਊਨਟੀ</developer_name>
|
||||
<developer_name xml:lang="pl">Społeczność KDE</developer_name>
|
||||
<developer_name xml:lang="pt">A Comunidade do KDE</developer_name>
|
||||
<developer_name xml:lang="pt-BR">A comunidade KDE</developer_name>
|
||||
<developer_name xml:lang="ru">Сообщество KDE</developer_name>
|
||||
<developer_name xml:lang="sk">KDE Komunita</developer_name>
|
||||
<developer_name xml:lang="sl">Skupnost KDE</developer_name>
|
||||
<developer_name xml:lang="sv">KDE-gemenskapen</developer_name>
|
||||
<developer_name xml:lang="ta">கே.டீ.யீ. சமூகம்</developer_name>
|
||||
<developer_name xml:lang="tr">KDE Topluluğu</developer_name>
|
||||
<developer_name xml:lang="uk">Спільнота KDE</developer_name>
|
||||
<developer_name xml:lang="x-test">xxThe KDE Communityxx</developer_name>
|
||||
<developer_name xml:lang="zh-CN">KDE 社区</developer_name>
|
||||
<developer_name xml:lang="zh-TW">KDE 社群</developer_name>
|
||||
<developer>
|
||||
<id>kde.org</id>
|
||||
<name>The KDE Community</name>
|
||||
<url>https://kde.org</url>
|
||||
</developer>
|
||||
<metadata_license>CC0-1.0</metadata_license>
|
||||
<project_license>GPL-3.0</project_license>
|
||||
<custom>
|
||||
@@ -314,7 +274,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<caption xml:lang="fi">Päänäkymä, jossa huoneluettelo, keskustelu ja huoneen tiedot</caption>
|
||||
<caption xml:lang="fr">Vue principale avec la liste des salons ainsi que des informations sur les salons et forums de discussions</caption>
|
||||
<caption xml:lang="gl">Vista principal coa lista de salas, a charla, e información da sala.</caption>
|
||||
<caption xml:lang="hu">A fő nézet a szobalistával, csevegéssel és szobainformációkkal</caption>
|
||||
<caption xml:lang="ia">Vista principal con lista de sala, chat e information de sala</caption>
|
||||
<caption xml:lang="it">Vista principale con elenco delle stanze, chat e informazioni sulla stanza</caption>
|
||||
<caption xml:lang="ka">მთავარი ხედი სურათების სიით, ჩატით და ოთახის ინფორმაციით</caption>
|
||||
@@ -344,7 +303,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<caption xml:lang="fi">Kirjautumisnäkymä</caption>
|
||||
<caption xml:lang="fr">Écran de connexion</caption>
|
||||
<caption xml:lang="gl">Pantalla de identificación.</caption>
|
||||
<caption xml:lang="hu">Bejelentkező képernyő</caption>
|
||||
<caption xml:lang="ia">Schermo de accesso</caption>
|
||||
<caption xml:lang="it">Schermata di accesso</caption>
|
||||
<caption xml:lang="ka">შესვლის ეკრანი</caption>
|
||||
@@ -366,26 +324,6 @@ to provide a convergent experience across multiple platforms.</p>
|
||||
<content_attribute id="social-chat">intense</content_attribute>
|
||||
</content_rating>
|
||||
<releases>
|
||||
<release version="24.02.2" date="2024-04-11"/>
|
||||
<release version="24.02.1" date="2024-03-21"/>
|
||||
<release version="24.02.0" date="2024-02-28">
|
||||
<url>https://kde.org/announcements/megarelease/6/#neochat</url>
|
||||
<description>
|
||||
<p>In the newest version, when launching the app, you will get a welcome page that lets you choose which account you want to use and lets you log in to other accounts. The welcome screen will also warn you when NeoChat cannot load an account.</p>
|
||||
<p>NeoChat will also let you register a new account directly from the app itself. Deactivating your Matrix account is also possible from within NeoChat.</p>
|
||||
<p>Spaces are a relatively new feature of Matrix that let you group chat channels together. This is used to improve the discoverability of rooms, manage large communities, or just tidy all the channels you are in. You can now do all this without leaving NeoChat.</p>
|
||||
<p>NeoChat won't let you miss any new notifications anymore. We added a new page that includes all your recent notifications and when NeoChat is closed, you will still be able to receive push notifications. The main timeline will let you know when more messages are loading and when you reach the end of it.</p>
|
||||
<p>More NeoChat Goodies</p>
|
||||
<ul>
|
||||
<li>QR Codes to share contacts</li>
|
||||
<li>Improved room upgrades</li>
|
||||
<li>Added button to reject invitation and ignore user</li>
|
||||
<li>Display device security details</li>
|
||||
<li>Added room security settings</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="23.08.5" date="2024-02-15"/>
|
||||
<release version="23.08.4" date="2023-12-07"/>
|
||||
<release version="23.08.3" date="2023-11-09"/>
|
||||
<release version="23.08.2" date="2023-10-12"/>
|
||||
|
||||
@@ -65,7 +65,7 @@ GenericName[ie]=Cliente de Matrix
|
||||
GenericName[it]=Client Matrix
|
||||
GenericName[ka]=Matrix -ის კლიენტი
|
||||
GenericName[ko]=Matrix 클라이언트
|
||||
GenericName[lt]=Matrix kliento programą
|
||||
GenericName[lt]=Matrix kliento programa
|
||||
GenericName[nl]=Matrix-client
|
||||
GenericName[nn]=Matrix-klient
|
||||
GenericName[pa]=ਮੈਟਰਿਕਸ ਕਲਾਈਂਟ
|
||||
|
||||
807
po/ar/neochat.po
807
po/ar/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
783
po/az/neochat.po
783
po/az/neochat.po
File diff suppressed because it is too large
Load Diff
779
po/ca/neochat.po
779
po/ca/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
762
po/cs/neochat.po
762
po/cs/neochat.po
File diff suppressed because it is too large
Load Diff
752
po/da/neochat.po
752
po/da/neochat.po
File diff suppressed because it is too large
Load Diff
788
po/de/neochat.po
788
po/de/neochat.po
File diff suppressed because it is too large
Load Diff
784
po/el/neochat.po
784
po/el/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
747
po/eo/neochat.po
747
po/eo/neochat.po
File diff suppressed because it is too large
Load Diff
766
po/es/neochat.po
766
po/es/neochat.po
File diff suppressed because it is too large
Load Diff
766
po/eu/neochat.po
766
po/eu/neochat.po
File diff suppressed because it is too large
Load Diff
1064
po/fi/neochat.po
1064
po/fi/neochat.po
File diff suppressed because it is too large
Load Diff
784
po/fr/neochat.po
784
po/fr/neochat.po
File diff suppressed because it is too large
Load Diff
1953
po/hu/neochat.po
1953
po/hu/neochat.po
File diff suppressed because it is too large
Load Diff
759
po/ia/neochat.po
759
po/ia/neochat.po
File diff suppressed because it is too large
Load Diff
785
po/id/neochat.po
785
po/id/neochat.po
File diff suppressed because it is too large
Load Diff
771
po/ie/neochat.po
771
po/ie/neochat.po
File diff suppressed because it is too large
Load Diff
773
po/it/neochat.po
773
po/it/neochat.po
File diff suppressed because it is too large
Load Diff
724
po/ja/neochat.po
724
po/ja/neochat.po
File diff suppressed because it is too large
Load Diff
759
po/ka/neochat.po
759
po/ka/neochat.po
File diff suppressed because it is too large
Load Diff
1015
po/ko/neochat.po
1015
po/ko/neochat.po
File diff suppressed because it is too large
Load Diff
724
po/lt/neochat.po
724
po/lt/neochat.po
File diff suppressed because it is too large
Load Diff
772
po/nl/neochat.po
772
po/nl/neochat.po
File diff suppressed because it is too large
Load Diff
740
po/nn/neochat.po
740
po/nn/neochat.po
File diff suppressed because it is too large
Load Diff
783
po/pa/neochat.po
783
po/pa/neochat.po
File diff suppressed because it is too large
Load Diff
826
po/pl/neochat.po
826
po/pl/neochat.po
File diff suppressed because it is too large
Load Diff
787
po/pt/neochat.po
787
po/pt/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
783
po/ru/neochat.po
783
po/ru/neochat.po
File diff suppressed because it is too large
Load Diff
982
po/sk/neochat.po
982
po/sk/neochat.po
File diff suppressed because it is too large
Load Diff
1363
po/sl/neochat.po
1363
po/sl/neochat.po
File diff suppressed because it is too large
Load Diff
780
po/sv/neochat.po
780
po/sv/neochat.po
File diff suppressed because it is too large
Load Diff
789
po/ta/neochat.po
789
po/ta/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
776
po/tr/neochat.po
776
po/tr/neochat.po
File diff suppressed because it is too large
Load Diff
771
po/uk/neochat.po
771
po/uk/neochat.po
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -144,6 +144,10 @@ add_library(neochat STATIC
|
||||
models/timelinemodel.cpp
|
||||
models/timelinemodel.h
|
||||
enums/pushrule.h
|
||||
models/itinerarymodel.cpp
|
||||
models/itinerarymodel.h
|
||||
proxycontroller.cpp
|
||||
proxycontroller.h
|
||||
)
|
||||
|
||||
qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
@@ -161,11 +165,10 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/UserInfoDesktop.qml
|
||||
qml/RoomPage.qml
|
||||
qml/RoomWindow.qml
|
||||
qml/JoinRoomPage.qml
|
||||
qml/ExploreRoomsPage.qml
|
||||
qml/ManualRoomDialog.qml
|
||||
qml/ExplorerDelegate.qml
|
||||
qml/InviteUserPage.qml
|
||||
qml/StartChatPage.qml
|
||||
qml/ImageEditorPage.qml
|
||||
qml/WelcomePage.qml
|
||||
qml/General.qml
|
||||
@@ -267,7 +270,7 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/EmojiTonesPicker.qml
|
||||
qml/EmojiDelegate.qml
|
||||
qml/EmojiGrid.qml
|
||||
qml/SearchPage.qml
|
||||
qml/RoomSearchPage.qml
|
||||
qml/LocationDelegate.qml
|
||||
qml/LocationChooser.qml
|
||||
qml/TimelineView.qml
|
||||
@@ -298,11 +301,16 @@ qt_add_qml_module(neochat URI org.kde.neochat NO_PLUGIN
|
||||
qml/NotificationsView.qml
|
||||
qml/LoadingDelegate.qml
|
||||
qml/TimelineEndDelegate.qml
|
||||
qml/SearchPage.qml
|
||||
qml/ServerComboBox.qml
|
||||
qml/UserSearchPage.qml
|
||||
RESOURCES
|
||||
qml/confetti.png
|
||||
qml/glowdot.png
|
||||
)
|
||||
|
||||
configure_file(config-neochat.h.in ${CMAKE_CURRENT_BINARY_DIR}/config-neochat.h)
|
||||
|
||||
if(WIN32)
|
||||
set_target_properties(neochat PROPERTIES OUTPUT_NAME "neochatlib")
|
||||
endif()
|
||||
@@ -316,6 +324,15 @@ ecm_qt_declare_logging_category(neochat
|
||||
EXPORT NEOCHAT
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(neochat
|
||||
HEADER "publicroomlist_logging.h"
|
||||
IDENTIFIER "PublicRoomList"
|
||||
CATEGORY_NAME "org.kde.neochat.publicroomlistmodel"
|
||||
DESCRIPTION "Neochat: publicroomlistmodel"
|
||||
DEFAULT_SEVERITY Info
|
||||
EXPORT NEOCHAT
|
||||
)
|
||||
|
||||
ecm_qt_declare_logging_category(neochat
|
||||
HEADER "eventhandler_logging.h"
|
||||
IDENTIFIER "EventHandling"
|
||||
@@ -365,10 +382,6 @@ if (NOT ANDROID AND NOT WIN32 AND NOT APPLE)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_RUNNER)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_X11)
|
||||
target_sources(neochat PRIVATE runner.cpp)
|
||||
|
||||
if (TARGET KUnifiedPush)
|
||||
target_sources(neochat PRIVATE fakerunner.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
target_include_directories(neochat PRIVATE ${CMAKE_BINARY_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/models ${CMAKE_CURRENT_SOURCE_DIR}/enums)
|
||||
@@ -505,7 +518,7 @@ if(NOT ANDROID)
|
||||
set_target_properties(neochat-app PROPERTIES OUTPUT_NAME "neochat")
|
||||
endif()
|
||||
|
||||
if(TARGET KF6::DBusAddons AND NOT WIN32)
|
||||
if(TARGET KF6::DBusAddons)
|
||||
target_link_libraries(neochat PUBLIC KF6::DBusAddons)
|
||||
target_compile_definitions(neochat PUBLIC -DHAVE_KDBUSADDONS)
|
||||
endif()
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include "models/actionsmodel.h"
|
||||
#include "neochatconfig.h"
|
||||
#include "texthandler.h"
|
||||
#include "utils.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
@@ -145,26 +144,6 @@ void ActionsHandler::handleMessage(const QString &text, QString handledText, Cha
|
||||
return;
|
||||
}
|
||||
|
||||
// We want to add back the <mx-reply> if it's in the original message but not in the edit, to preserve the reply.
|
||||
for (auto it = m_room->messageEvents().crbegin(); it != m_room->messageEvents().crend(); it++) {
|
||||
if (const auto event = eventCast<const RoomMessageEvent>(&**it)) {
|
||||
if (event->senderId() == m_room->localUser()->id() && event->hasTextContent()) {
|
||||
QString originalString;
|
||||
if (event->content()) {
|
||||
originalString = static_cast<const Quotient::EventContent::TextContent *>(event->content())->body;
|
||||
} else {
|
||||
originalString = event->plainBody();
|
||||
}
|
||||
|
||||
const QRegularExpression exp(TextRegex::removeRichReply);
|
||||
const auto match = exp.match(originalString);
|
||||
if (match.hasCaptured(0) && !handledText.contains(TextRegex::removeRichReply)) {
|
||||
handledText.prepend(match.captured(0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_room->postMessage(text, handledText, messageType, chatBarCache->replyId(), chatBarCache->editId(), chatBarCache->threadId());
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
#include "chatbarcache.h"
|
||||
|
||||
#include "chatdocumenthandler.h"
|
||||
#include "eventhandler.h"
|
||||
#include "neochatroom.h"
|
||||
|
||||
@@ -119,7 +118,7 @@ QString ChatBarCache::relationMessage() const
|
||||
eventhandler.setRoom(room);
|
||||
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
|
||||
eventhandler.setEvent(&**event);
|
||||
return eventhandler.getMarkdownBody();
|
||||
return eventhandler.getPlainBody();
|
||||
}
|
||||
return {};
|
||||
}
|
||||
@@ -165,54 +164,6 @@ QList<Mention> *ChatBarCache::mentions()
|
||||
return &m_mentions;
|
||||
}
|
||||
|
||||
void ChatBarCache::updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler)
|
||||
{
|
||||
documentHandler->setDocument(document);
|
||||
|
||||
if (parent() == nullptr) {
|
||||
qWarning() << "ChatBarCache created with no parent, a NeoChatRoom must be set as the parent on creation.";
|
||||
return;
|
||||
}
|
||||
if (m_relationId.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
auto room = dynamic_cast<NeoChatRoom *>(parent());
|
||||
if (room == nullptr) {
|
||||
qWarning() << "ChatBarCache created with incorrect parent, a NeoChatRoom must be set as the parent on creation.";
|
||||
return;
|
||||
}
|
||||
|
||||
if (auto event = room->findInTimeline(m_relationId); event != room->historyEdge()) {
|
||||
if (const auto &roomMessageEvent = &*event->viewAs<Quotient::RoomMessageEvent>()) {
|
||||
// Replaces the mentions that are baked into the HTML but plaintext in the original markdown
|
||||
const QRegularExpression re(QStringLiteral(R"lit(<a\shref="https:\/\/matrix.to\/#\/([\S]*)"\s?>([\S]*)<\/a>)lit"));
|
||||
|
||||
m_mentions.clear();
|
||||
|
||||
int linkSize = 0;
|
||||
auto matches = re.globalMatch(EventHandler::rawMessageBody(*roomMessageEvent));
|
||||
while (matches.hasNext()) {
|
||||
const QRegularExpressionMatch match = matches.next();
|
||||
if (match.hasMatch()) {
|
||||
const QString id = match.captured(1);
|
||||
const QString name = match.captured(2);
|
||||
|
||||
const int position = match.capturedStart(0) - linkSize;
|
||||
const int end = position + name.length();
|
||||
linkSize += match.capturedLength(0) - name.length();
|
||||
|
||||
QTextCursor cursor(documentHandler->document()->textDocument());
|
||||
cursor.setPosition(position);
|
||||
cursor.setPosition(end, QTextCursor::KeepAnchor);
|
||||
cursor.setKeepPositionOnInsert(true);
|
||||
|
||||
m_mentions.push_back(Mention{.cursor = cursor, .text = name, .start = position, .position = end, .id = id});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QString ChatBarCache::savedText() const
|
||||
{
|
||||
return m_savedText;
|
||||
|
||||
@@ -5,11 +5,8 @@
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
#include <QQuickTextDocument>
|
||||
#include <QTextCursor>
|
||||
|
||||
class ChatDocumentHandler;
|
||||
|
||||
/**
|
||||
* @brief Defines a user mention in the current chat or edit text.
|
||||
*/
|
||||
@@ -177,11 +174,6 @@ public:
|
||||
*/
|
||||
QList<Mention> *mentions();
|
||||
|
||||
/**
|
||||
* @brief Update the mentions in @p document when editing a message.
|
||||
*/
|
||||
Q_INVOKABLE void updateMentions(QQuickTextDocument *document, ChatDocumentHandler *documentHandler);
|
||||
|
||||
/**
|
||||
* @brief Get the saved chat bar text.
|
||||
*/
|
||||
|
||||
8
src/config-neochat.h.in
Normal file
8
src/config-neochat.h.in
Normal file
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#define CMAKE_INSTALL_FULL_LIBEXECDIR_KF6 "${KDE_INSTALL_FULL_LIBEXECDIR_KF}"
|
||||
@@ -23,12 +23,12 @@
|
||||
#include <Quotient/csapi/logout.h>
|
||||
#include <Quotient/csapi/notifications.h>
|
||||
#include <Quotient/eventstats.h>
|
||||
#include <Quotient/jobs/downloadfilejob.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
#include "neochatroom.h"
|
||||
#include "notificationsmanager.h"
|
||||
#include "proxycontroller.h"
|
||||
#include "roommanager.h"
|
||||
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
@@ -46,7 +46,7 @@ Controller::Controller(QObject *parent)
|
||||
{
|
||||
Connection::setRoomType<NeoChatRoom>();
|
||||
|
||||
setApplicationProxy();
|
||||
ProxyController::instance().setApplicationProxy();
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
setQuitOnLastWindowClosed();
|
||||
@@ -178,12 +178,10 @@ void Controller::invokeLogin()
|
||||
}
|
||||
|
||||
auto connection = new NeoChatConnection(account.homeserver());
|
||||
m_connectionsLoading[accountId] = connection;
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection, accountId] {
|
||||
connect(connection, &NeoChatConnection::connected, this, [this, connection] {
|
||||
connection->loadState();
|
||||
addConnection(connection);
|
||||
m_accountsLoading.removeAll(connection->userId());
|
||||
m_connectionsLoading.remove(accountId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
});
|
||||
connect(connection, &NeoChatConnection::networkError, this, [this](const QString &error, const QString &, int, int) {
|
||||
@@ -287,28 +285,10 @@ void Controller::setActiveConnection(NeoChatConnection *connection)
|
||||
if (connection == m_connection) {
|
||||
return;
|
||||
}
|
||||
if (m_connection != nullptr) {
|
||||
disconnect(m_connection, &NeoChatConnection::syncError, this, nullptr);
|
||||
disconnect(m_connection, &NeoChatConnection::accountDataChanged, this, nullptr);
|
||||
}
|
||||
m_connection = connection;
|
||||
if (connection != nullptr) {
|
||||
connect(connection, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
|
||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
|
||||
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||
}
|
||||
});
|
||||
}
|
||||
NeoChatConfig::self()->save();
|
||||
Q_EMIT activeConnectionChanged();
|
||||
}
|
||||
|
||||
void Controller::forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item)
|
||||
{
|
||||
// HACK: Workaround bug QTBUG 93281
|
||||
connect(textDocument->textDocument(), SIGNAL(imagesLoaded()), item, SLOT(updateWholeDocument()));
|
||||
}
|
||||
|
||||
void Controller::listenForNotifications()
|
||||
{
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
@@ -330,36 +310,6 @@ void Controller::listenForNotifications()
|
||||
#endif
|
||||
}
|
||||
|
||||
void Controller::setApplicationProxy()
|
||||
{
|
||||
NeoChatConfig *cfg = NeoChatConfig::self();
|
||||
QNetworkProxy proxy;
|
||||
|
||||
// type match to ProxyType from neochatconfig.kcfg
|
||||
switch (cfg->proxyType()) {
|
||||
case 1: // HTTP
|
||||
proxy.setType(QNetworkProxy::HttpProxy);
|
||||
proxy.setHostName(cfg->proxyHost());
|
||||
proxy.setPort(cfg->proxyPort());
|
||||
proxy.setUser(cfg->proxyUser());
|
||||
proxy.setPassword(cfg->proxyPassword());
|
||||
break;
|
||||
case 2: // SOCKS 5
|
||||
proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||
proxy.setHostName(cfg->proxyHost());
|
||||
proxy.setPort(cfg->proxyPort());
|
||||
proxy.setUser(cfg->proxyUser());
|
||||
proxy.setPassword(cfg->proxyPassword());
|
||||
break;
|
||||
case 0: // System Default
|
||||
default:
|
||||
// do nothing
|
||||
break;
|
||||
}
|
||||
|
||||
QNetworkProxy::setApplicationProxy(proxy);
|
||||
}
|
||||
|
||||
bool Controller::isFlatpak() const
|
||||
{
|
||||
#ifdef NEOCHAT_FLATPAK
|
||||
@@ -380,13 +330,3 @@ void Controller::setTestMode(bool test)
|
||||
{
|
||||
testMode = test;
|
||||
}
|
||||
|
||||
void Controller::removeConnection(const QString &userId)
|
||||
{
|
||||
if (m_connectionsLoading.contains(userId) && m_connectionsLoading[userId]) {
|
||||
auto connection = m_connectionsLoading[userId];
|
||||
m_accountsLoading.removeAll(userId);
|
||||
Q_EMIT accountsLoadingChanged();
|
||||
SettingsGroup("Accounts"_ls).remove(userId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,22 +84,8 @@ public:
|
||||
|
||||
[[nodiscard]] bool supportSystemTray() const;
|
||||
|
||||
/**
|
||||
* @brief Sets the QNetworkProxy for the application.
|
||||
*
|
||||
* @sa QNetworkProxy::setApplicationProxy
|
||||
*/
|
||||
Q_INVOKABLE void setApplicationProxy();
|
||||
|
||||
bool isFlatpak() const;
|
||||
|
||||
/**
|
||||
* @brief Force a QQuickTextDocument to refresh when images are loaded.
|
||||
*
|
||||
* HACK: This is a workaround for QTBUG 93281.
|
||||
*/
|
||||
Q_INVOKABLE void forceRefreshTextDocument(QQuickTextDocument *textDocument, QQuickItem *item);
|
||||
|
||||
/**
|
||||
* @brief Start listening for notifications in dbus-activated mode.
|
||||
* These notifications will quit the application when closed.
|
||||
@@ -110,8 +96,6 @@ public:
|
||||
|
||||
static void setTestMode(bool testMode);
|
||||
|
||||
Q_INVOKABLE void removeConnection(const QString &userId);
|
||||
|
||||
private:
|
||||
explicit Controller(QObject *parent = nullptr);
|
||||
|
||||
@@ -125,7 +109,6 @@ private:
|
||||
|
||||
Quotient::AccountRegistry m_accountRegistry;
|
||||
QStringList m_accountsLoading;
|
||||
QMap<QString, QPointer<Quotient::Connection>> m_connectionsLoading;
|
||||
QString m_endpoint;
|
||||
|
||||
private Q_SLOTS:
|
||||
|
||||
@@ -300,27 +300,6 @@ bool EventHandler::isHidden()
|
||||
return false;
|
||||
}
|
||||
|
||||
QString EventHandler::rawMessageBody(const Quotient::RoomMessageEvent &event)
|
||||
{
|
||||
if (event.hasFileContent()) {
|
||||
auto fileCaption = event.content()->fileInfo()->originalName;
|
||||
if (fileCaption.isEmpty()) {
|
||||
fileCaption = event.plainBody();
|
||||
} else if (event.content()->fileInfo()->originalName != event.plainBody()) {
|
||||
fileCaption = event.plainBody() + " | "_ls + fileCaption;
|
||||
}
|
||||
return fileCaption;
|
||||
}
|
||||
|
||||
QString body;
|
||||
if (event.hasTextContent() && event.content()) {
|
||||
body = static_cast<const MessageEventContent::TextContent *>(event.content())->body;
|
||||
} else {
|
||||
body = event.plainBody();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
QString EventHandler::getRichBody(bool stripNewlines) const
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
@@ -339,22 +318,6 @@ QString EventHandler::getPlainBody(bool stripNewlines) const
|
||||
return getBody(m_event, Qt::PlainText, stripNewlines);
|
||||
}
|
||||
|
||||
QString EventHandler::getMarkdownBody() const
|
||||
{
|
||||
if (m_event == nullptr) {
|
||||
qCWarning(EventHandling) << "getMarkdownBody called with m_event set to nullptr.";
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!m_event->is<RoomMessageEvent>()) {
|
||||
qCWarning(EventHandling) << "getMarkdownBody called when m_event isn't a RoomMessageEvent.";
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto roomMessageEvent = eventCast<const RoomMessageEvent>(m_event);
|
||||
return roomMessageEvent->plainBody();
|
||||
}
|
||||
|
||||
QString EventHandler::getBody(const Quotient::RoomEvent *event, Qt::TextFormat format, bool stripNewlines) const
|
||||
{
|
||||
if (event->isRedacted()) {
|
||||
|
||||
@@ -159,14 +159,6 @@ public:
|
||||
*/
|
||||
bool isHidden();
|
||||
|
||||
/**
|
||||
* @brief Output a string for the room message content without any formatting.
|
||||
*
|
||||
* This is the content of the formatted_body key if present or the body key if
|
||||
* not.
|
||||
*/
|
||||
static QString rawMessageBody(const Quotient::RoomMessageEvent &event);
|
||||
|
||||
/**
|
||||
* @brief Output a string for the message content ready for display in a rich text field.
|
||||
*
|
||||
@@ -199,13 +191,6 @@ public:
|
||||
*/
|
||||
QString getPlainBody(bool stripNewlines = false) const;
|
||||
|
||||
/**
|
||||
* @brief Output the original body for the message content, useful for editing the original message.
|
||||
*
|
||||
* The event type must be a room message event.
|
||||
*/
|
||||
QString getMarkdownBody() const;
|
||||
|
||||
/**
|
||||
* @brief Output a generic string for the message content ready for display.
|
||||
*
|
||||
|
||||
@@ -1,36 +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 "fakerunner.h"
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QDBusMetaType>
|
||||
|
||||
Q_SCRIPTABLE RemoteActions FakeRunner::Actions()
|
||||
{
|
||||
QCoreApplication::quit();
|
||||
return {};
|
||||
}
|
||||
|
||||
Q_SCRIPTABLE RemoteMatches FakeRunner::Match(const QString &searchTerm)
|
||||
{
|
||||
QCoreApplication::quit();
|
||||
return {};
|
||||
}
|
||||
|
||||
Q_SCRIPTABLE void FakeRunner::Run(const QString &id, const QString &actionId)
|
||||
{
|
||||
QCoreApplication::quit();
|
||||
}
|
||||
|
||||
FakeRunner::FakeRunner()
|
||||
: QObject()
|
||||
{
|
||||
qDBusRegisterMetaType<RemoteMatch>();
|
||||
qDBusRegisterMetaType<RemoteMatches>();
|
||||
qDBusRegisterMetaType<RemoteAction>();
|
||||
qDBusRegisterMetaType<RemoteActions>();
|
||||
qDBusRegisterMetaType<RemoteImage>();
|
||||
}
|
||||
|
||||
#include "moc_fakerunner.cpp"
|
||||
@@ -1,31 +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 <QDBusContext>
|
||||
|
||||
#include "runner.h"
|
||||
|
||||
/**
|
||||
* This is a close-to-identical copy of the regular Runner interface,
|
||||
* only used when activated for push notifications. This stubs it out so
|
||||
* Plasma Search and Kickoff doesn't accidentally activate the push notification
|
||||
* service.
|
||||
*
|
||||
* @sa Runner
|
||||
*/
|
||||
class FakeRunner : public QObject, protected QDBusContext
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_CLASSINFO("D-Bus Interface", "org.kde.krunner1")
|
||||
|
||||
public:
|
||||
Q_SCRIPTABLE RemoteActions Actions();
|
||||
|
||||
Q_SCRIPTABLE RemoteMatches Match(const QString &searchTerm);
|
||||
|
||||
Q_SCRIPTABLE void Run(const QString &id, const QString &actionId);
|
||||
|
||||
FakeRunner();
|
||||
};
|
||||
13
src/main.cpp
13
src/main.cpp
@@ -48,11 +48,6 @@
|
||||
#ifdef HAVE_RUNNER
|
||||
#include "runner.h"
|
||||
#include <QDBusConnection>
|
||||
#include <QDBusMetaType>
|
||||
#endif
|
||||
|
||||
#if defined(HAVE_RUNNER) && defined(HAVE_KUNIFIEDPUSH)
|
||||
#include "fakerunner.h"
|
||||
#endif
|
||||
|
||||
#ifdef Q_OS_WINDOWS
|
||||
@@ -201,14 +196,6 @@ int main(int argc, char *argv[])
|
||||
// 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.
|
||||
// Because KRunner may call us on the D-Bus (under the same service name org.kde.neochat) then it may
|
||||
// accidentally activate us for push notifications instead. If this happens, then immediately quit if the fake
|
||||
// runner is called.
|
||||
QDBusConnection::sessionBus().registerObject("/RoomRunner"_ls, new FakeRunner(), QDBusConnection::ExportScriptableContents);
|
||||
#endif
|
||||
|
||||
Controller::listenForNotifications();
|
||||
return QCoreApplication::exec();
|
||||
}
|
||||
|
||||
@@ -354,12 +354,17 @@ QList<ActionsModel::Action> actions{
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
return QString();
|
||||
}
|
||||
if (room->connection()->ignoredUsers().contains(text)) {
|
||||
auto user = room->connection()->users()[text];
|
||||
if (room->connection()->ignoredUsers().contains(user->id())) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is already ignored.", "%1 is already ignored.", text));
|
||||
return QString();
|
||||
}
|
||||
room->connection()->addToIgnoredUsers(room->connection()->user(text));
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
|
||||
if (user) {
|
||||
room->connection()->addToIgnoredUsers(user);
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is now ignored", "%1 is now ignored.", text));
|
||||
} else {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not a known user", "%1 is not a known user.", text));
|
||||
}
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
@@ -377,12 +382,17 @@ QList<ActionsModel::Action> actions{
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("'<text>' does not look like a matrix id.", "'%1' does not look like a matrix id.", text));
|
||||
return QString();
|
||||
}
|
||||
if (!room->connection()->ignoredUsers().contains(text)) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
|
||||
return QString();
|
||||
auto user = room->connection()->users()[text];
|
||||
if (user) {
|
||||
if (!room->connection()->ignoredUsers().contains(user->id())) {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Info, i18nc("<username> is not ignored.", "%1 is not ignored.", text));
|
||||
return QString();
|
||||
}
|
||||
room->connection()->removeFromIgnoredUsers(user);
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
|
||||
} else {
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Error, i18nc("<username> is not a known user", "%1 is not a known user.", text));
|
||||
}
|
||||
room->connection()->removeFromIgnoredUsers(room->connection()->user(text));
|
||||
Q_EMIT room->showMessage(NeoChatRoom::Positive, i18nc("<username> is no longer ignored.", "%1 is no longer ignored.", text));
|
||||
return QString();
|
||||
},
|
||||
false,
|
||||
|
||||
163
src/models/itinerarymodel.cpp
Normal file
163
src/models/itinerarymodel.cpp
Normal file
@@ -0,0 +1,163 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "itinerarymodel.h"
|
||||
|
||||
#include <QProcess>
|
||||
|
||||
#include "config-neochat.h"
|
||||
|
||||
#ifndef Q_OS_ANDROID
|
||||
#include <KIO/ApplicationLauncherJob>
|
||||
#endif
|
||||
|
||||
ItineraryModel::ItineraryModel(QObject *parent)
|
||||
: QAbstractListModel(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void ItineraryModel::setConnection(NeoChatConnection *connection)
|
||||
{
|
||||
if (m_connection == connection) {
|
||||
return;
|
||||
}
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
NeoChatConnection *ItineraryModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
QVariant ItineraryModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
auto row = index.row();
|
||||
auto data = m_data[row];
|
||||
if (role == NameRole) {
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("TrainReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("trainNumber")];
|
||||
}
|
||||
if (data[QStringLiteral("@type")] == QStringLiteral("LodgingReservation")) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("name")];
|
||||
}
|
||||
}
|
||||
if (role == TypeRole) {
|
||||
return data[QStringLiteral("@type")];
|
||||
}
|
||||
if (role == DepartureStationRole) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("departureStation")][QStringLiteral("name")];
|
||||
}
|
||||
if (role == ArrivalStationRole) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalStation")][QStringLiteral("name")];
|
||||
}
|
||||
if (role == DepartureTimeRole) {
|
||||
const auto &time = data[QStringLiteral("reservationFor")][QStringLiteral("departureTime")];
|
||||
auto dateTime = (time.isString() ? time : time[QStringLiteral("@value")]).toVariant().toDateTime();
|
||||
if (const auto &timeZone = time[QStringLiteral("timezone")].toString(); timeZone.length() > 0) {
|
||||
dateTime.setTimeZone(QTimeZone(timeZone.toLatin1().data()));
|
||||
}
|
||||
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
|
||||
}
|
||||
if (role == ArrivalTimeRole) {
|
||||
const auto &time = data[QStringLiteral("reservationFor")][QStringLiteral("arrivalTime")];
|
||||
auto dateTime = (time.isString() ? time : time[QStringLiteral("@value")]).toVariant().toDateTime();
|
||||
if (const auto &timeZone = time[QStringLiteral("timezone")].toString(); timeZone.length() > 0) {
|
||||
dateTime.setTimeZone(QTimeZone(timeZone.toLatin1().data()));
|
||||
}
|
||||
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
|
||||
}
|
||||
if (role == AddressRole) {
|
||||
const auto &addressData = data[QStringLiteral("reservationFor")][QStringLiteral("address")];
|
||||
return QStringLiteral("%1 - %2 %3 %4")
|
||||
.arg(addressData[QStringLiteral("streetAddress")].toString(),
|
||||
addressData[QStringLiteral("postalCode")].toString(),
|
||||
addressData[QStringLiteral("addressLocality")].toString(),
|
||||
addressData[QStringLiteral("addressCountry")].toString());
|
||||
}
|
||||
if (role == StartTimeRole) {
|
||||
auto dateTime = data[QStringLiteral("checkinTime")][QStringLiteral("@value")].toVariant().toDateTime();
|
||||
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
|
||||
}
|
||||
if (role == EndTimeRole) {
|
||||
auto dateTime = data[QStringLiteral("checkoutTime")][QStringLiteral("@value")].toVariant().toDateTime();
|
||||
return dateTime.toString(QLocale::system().dateTimeFormat(QLocale::ShortFormat));
|
||||
}
|
||||
if (role == DeparturePlatformRole) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("departurePlatform")];
|
||||
}
|
||||
if (role == ArrivalPlatformRole) {
|
||||
return data[QStringLiteral("reservationFor")][QStringLiteral("arrivalPlatform")];
|
||||
}
|
||||
if (role == CoachRole) {
|
||||
return data[QStringLiteral("reservedTicket")][QStringLiteral("ticketedSeat")][QStringLiteral("seatSection")];
|
||||
}
|
||||
if (role == SeatRole) {
|
||||
return data[QStringLiteral("reservedTicket")][QStringLiteral("ticketedSeat")][QStringLiteral("seatNumber")];
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
int ItineraryModel::rowCount(const QModelIndex &parent) const
|
||||
{
|
||||
Q_UNUSED(parent);
|
||||
return m_data.size();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> ItineraryModel::roleNames() const
|
||||
{
|
||||
return {
|
||||
{NameRole, "name"},
|
||||
{TypeRole, "type"},
|
||||
{DepartureStationRole, "departureStation"},
|
||||
{ArrivalStationRole, "arrivalStation"},
|
||||
{DepartureTimeRole, "departureTime"},
|
||||
{ArrivalTimeRole, "arrivalTime"},
|
||||
{AddressRole, "address"},
|
||||
{StartTimeRole, "startTime"},
|
||||
{EndTimeRole, "endTime"},
|
||||
{DeparturePlatformRole, "departurePlatform"},
|
||||
{ArrivalPlatformRole, "arrivalPlatform"},
|
||||
{CoachRole, "coach"},
|
||||
{SeatRole, "seat"},
|
||||
};
|
||||
}
|
||||
|
||||
QString ItineraryModel::path() const
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
void ItineraryModel::setPath(const QString &path)
|
||||
{
|
||||
if (path == m_path) {
|
||||
return;
|
||||
}
|
||||
m_path = path;
|
||||
Q_EMIT pathChanged();
|
||||
loadData();
|
||||
}
|
||||
|
||||
void ItineraryModel::loadData()
|
||||
{
|
||||
auto process = new QProcess(this);
|
||||
process->start(QLatin1String(CMAKE_INSTALL_FULL_LIBEXECDIR_KF6) + QLatin1String("/kitinerary-extractor"), {m_path.mid(7)});
|
||||
connect(process, &QProcess::finished, this, [this, process]() {
|
||||
auto data = process->readAllStandardOutput();
|
||||
beginResetModel();
|
||||
m_data = QJsonDocument::fromJson(data).array();
|
||||
endResetModel();
|
||||
});
|
||||
}
|
||||
|
||||
void ItineraryModel::sendToItinerary()
|
||||
{
|
||||
#ifndef Q_OS_ANDROID
|
||||
auto job = new KIO::ApplicationLauncherJob(KService::serviceByDesktopName(QStringLiteral("org.kde.itinerary")));
|
||||
job->setUrls({QUrl::fromLocalFile(m_path.mid(7))});
|
||||
job->start();
|
||||
#endif
|
||||
}
|
||||
62
src/models/itinerarymodel.h
Normal file
62
src/models/itinerarymodel.h
Normal file
@@ -0,0 +1,62 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QPointer>
|
||||
#include <QQmlEngine>
|
||||
#include <QString>
|
||||
|
||||
#include "neochatconnection.h"
|
||||
|
||||
class ItineraryModel : public QAbstractListModel
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
|
||||
Q_PROPERTY(NeoChatConnection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged)
|
||||
|
||||
public:
|
||||
enum Roles {
|
||||
NameRole = Qt::DisplayRole,
|
||||
TypeRole,
|
||||
DepartureStationRole,
|
||||
ArrivalStationRole,
|
||||
DepartureTimeRole,
|
||||
ArrivalTimeRole,
|
||||
AddressRole,
|
||||
StartTimeRole,
|
||||
EndTimeRole,
|
||||
DeparturePlatformRole,
|
||||
ArrivalPlatformRole,
|
||||
CoachRole,
|
||||
SeatRole,
|
||||
};
|
||||
Q_ENUM(Roles)
|
||||
explicit ItineraryModel(QObject *parent = nullptr);
|
||||
|
||||
void setConnection(NeoChatConnection *connection);
|
||||
NeoChatConnection *connection() const;
|
||||
|
||||
QVariant data(const QModelIndex &index, int role) const override;
|
||||
int rowCount(const QModelIndex &parent = {}) const override;
|
||||
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
QString path() const;
|
||||
void setPath(const QString &path);
|
||||
|
||||
Q_INVOKABLE void sendToItinerary();
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void pathChanged();
|
||||
|
||||
private:
|
||||
QPointer<NeoChatConnection> m_connection;
|
||||
QJsonArray m_data;
|
||||
QString m_path;
|
||||
void loadData();
|
||||
};
|
||||
@@ -5,6 +5,8 @@
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
|
||||
#include "publicroomlist_logging.h"
|
||||
|
||||
using namespace Quotient;
|
||||
|
||||
PublicRoomListModel::PublicRoomListModel(QObject *parent)
|
||||
@@ -41,7 +43,7 @@ void PublicRoomListModel::setConnection(Connection *conn)
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
|
||||
if (m_connection) {
|
||||
@@ -50,7 +52,6 @@ void PublicRoomListModel::setConnection(Connection *conn)
|
||||
|
||||
Q_EMIT connectionChanged();
|
||||
Q_EMIT serverChanged();
|
||||
Q_EMIT hasMoreChanged();
|
||||
}
|
||||
|
||||
QString PublicRoomListModel::server() const
|
||||
@@ -71,14 +72,13 @@ void PublicRoomListModel::setServer(const QString &value)
|
||||
nextBatch = QString();
|
||||
attempted = false;
|
||||
rooms.clear();
|
||||
Q_EMIT loadingChanged();
|
||||
|
||||
endResetModel();
|
||||
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
|
||||
if (m_connection) {
|
||||
@@ -86,42 +86,30 @@ void PublicRoomListModel::setServer(const QString &value)
|
||||
}
|
||||
|
||||
Q_EMIT serverChanged();
|
||||
Q_EMIT hasMoreChanged();
|
||||
}
|
||||
|
||||
QString PublicRoomListModel::keyword() const
|
||||
QString PublicRoomListModel::searchText() const
|
||||
{
|
||||
return m_keyword;
|
||||
return m_searchText;
|
||||
}
|
||||
|
||||
void PublicRoomListModel::setKeyword(const QString &value)
|
||||
void PublicRoomListModel::setSearchText(const QString &value)
|
||||
{
|
||||
if (m_keyword == value) {
|
||||
if (m_searchText == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_keyword = value;
|
||||
|
||||
beginResetModel();
|
||||
m_searchText = value;
|
||||
Q_EMIT searchTextChanged();
|
||||
|
||||
nextBatch = QString();
|
||||
attempted = false;
|
||||
rooms.clear();
|
||||
|
||||
endResetModel();
|
||||
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
|
||||
if (m_connection) {
|
||||
next();
|
||||
}
|
||||
|
||||
Q_EMIT keywordChanged();
|
||||
Q_EMIT hasMoreChanged();
|
||||
}
|
||||
|
||||
bool PublicRoomListModel::showOnlySpaces() const
|
||||
@@ -138,15 +126,28 @@ void PublicRoomListModel::setShowOnlySpaces(bool showOnlySpaces)
|
||||
Q_EMIT showOnlySpacesChanged();
|
||||
}
|
||||
|
||||
void PublicRoomListModel::next(int count)
|
||||
void PublicRoomListModel::search(int limit)
|
||||
{
|
||||
if (count < 1) {
|
||||
if (limit < 1 || attempted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (job) {
|
||||
qDebug() << "PublicRoomListModel: Other jobs running, ignore";
|
||||
qCDebug(PublicRoomList) << "Other job running, ignore";
|
||||
return;
|
||||
}
|
||||
|
||||
next(limit);
|
||||
}
|
||||
|
||||
void PublicRoomListModel::next(int limit)
|
||||
{
|
||||
if (m_connection == nullptr || limit < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (job) {
|
||||
qCDebug(PublicRoomList) << "Other job running, ignore";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -154,11 +155,17 @@ void PublicRoomListModel::next(int count)
|
||||
if (m_showOnlySpaces) {
|
||||
roomTypes += QLatin1String("m.space");
|
||||
}
|
||||
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, count, nextBatch, QueryPublicRoomsJob::Filter{m_keyword, roomTypes});
|
||||
Q_EMIT loadingChanged();
|
||||
job = m_connection->callApi<QueryPublicRoomsJob>(m_server, limit, nextBatch, QueryPublicRoomsJob::Filter{m_searchText, roomTypes});
|
||||
Q_EMIT searchingChanged();
|
||||
|
||||
connect(job, &BaseJob::finished, this, [this] {
|
||||
attempted = true;
|
||||
if (!attempted) {
|
||||
beginResetModel();
|
||||
rooms.clear();
|
||||
endResetModel();
|
||||
|
||||
attempted = true;
|
||||
}
|
||||
|
||||
if (job->status() == BaseJob::Success) {
|
||||
nextBatch = job->nextBatch();
|
||||
@@ -166,14 +173,10 @@ void PublicRoomListModel::next(int count)
|
||||
this->beginInsertRows({}, rooms.count(), rooms.count() + job->chunk().count() - 1);
|
||||
rooms.append(job->chunk());
|
||||
this->endInsertRows();
|
||||
|
||||
if (job->nextBatch().isEmpty()) {
|
||||
Q_EMIT hasMoreChanged();
|
||||
}
|
||||
}
|
||||
|
||||
this->job = nullptr;
|
||||
Q_EMIT loadingChanged();
|
||||
Q_EMIT searchingChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -184,8 +187,7 @@ QVariant PublicRoomListModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
|
||||
if (index.row() >= rooms.count()) {
|
||||
qDebug() << "PublicRoomListModel, something's wrong: index.row() >= "
|
||||
"rooms.count()";
|
||||
qCDebug(PublicRoomList) << "something's wrong: index.row() >= rooms.count()";
|
||||
return {};
|
||||
}
|
||||
auto room = rooms.at(index.row());
|
||||
@@ -271,12 +273,19 @@ int PublicRoomListModel::rowCount(const QModelIndex &parent) const
|
||||
return rooms.count();
|
||||
}
|
||||
|
||||
bool PublicRoomListModel::hasMore() const
|
||||
bool PublicRoomListModel::canFetchMore(const QModelIndex &parent) const
|
||||
{
|
||||
return !(attempted && nextBatch.isEmpty());
|
||||
Q_UNUSED(parent)
|
||||
return !nextBatch.isEmpty();
|
||||
}
|
||||
|
||||
bool PublicRoomListModel::loading() const
|
||||
void PublicRoomListModel::fetchMore(const QModelIndex &parent)
|
||||
{
|
||||
Q_UNUSED(parent)
|
||||
next();
|
||||
}
|
||||
|
||||
bool PublicRoomListModel::searching() const
|
||||
{
|
||||
return job != nullptr;
|
||||
}
|
||||
|
||||
@@ -41,9 +41,9 @@ class PublicRoomListModel : public QAbstractListModel
|
||||
Q_PROPERTY(QString server READ server WRITE setServer NOTIFY serverChanged)
|
||||
|
||||
/**
|
||||
* @brief The filter keyword for the list of public rooms.
|
||||
* @brief The text to search the public room list for.
|
||||
*/
|
||||
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
|
||||
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether only space rooms should be shown.
|
||||
@@ -51,14 +51,9 @@ class PublicRoomListModel : public QAbstractListModel
|
||||
Q_PROPERTY(bool showOnlySpaces READ showOnlySpaces WRITE setShowOnlySpaces NOTIFY showOnlySpacesChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the model has more items to load.
|
||||
* @brief Whether the model is searching.
|
||||
*/
|
||||
Q_PROPERTY(bool hasMore READ hasMore NOTIFY hasMoreChanged)
|
||||
|
||||
/**
|
||||
* @biref Whether the model is still loading.
|
||||
*/
|
||||
Q_PROPERTY(bool loading READ loading NOTIFY loadingChanged)
|
||||
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
@@ -105,31 +100,38 @@ public:
|
||||
[[nodiscard]] QString server() const;
|
||||
void setServer(const QString &value);
|
||||
|
||||
[[nodiscard]] QString keyword() const;
|
||||
void setKeyword(const QString &value);
|
||||
[[nodiscard]] QString searchText() const;
|
||||
void setSearchText(const QString &searchText);
|
||||
|
||||
[[nodiscard]] bool showOnlySpaces() const;
|
||||
void setShowOnlySpaces(bool showOnlySpaces);
|
||||
|
||||
[[nodiscard]] bool hasMore() const;
|
||||
[[nodiscard]] bool searching() const;
|
||||
|
||||
[[nodiscard]] bool loading() const;
|
||||
/**
|
||||
* @brief Search the room directory.
|
||||
*
|
||||
* @param limit the maximum number of rooms to load.
|
||||
*/
|
||||
Q_INVOKABLE void search(int limit = 50);
|
||||
|
||||
private:
|
||||
QPointer<Quotient::Connection> m_connection = nullptr;
|
||||
QString m_server;
|
||||
QString m_searchText;
|
||||
bool m_showOnlySpaces = false;
|
||||
|
||||
/**
|
||||
* @brief Load the next set of rooms.
|
||||
*
|
||||
* @param count the maximum number of rooms to load.
|
||||
* @param limit the maximum number of rooms to load.
|
||||
*/
|
||||
Q_INVOKABLE void next(int count = 50);
|
||||
|
||||
private:
|
||||
Quotient::Connection *m_connection = nullptr;
|
||||
QString m_server;
|
||||
QString m_keyword;
|
||||
bool m_showOnlySpaces = false;
|
||||
void next(int limit = 50);
|
||||
bool canFetchMore(const QModelIndex &parent) const override;
|
||||
void fetchMore(const QModelIndex &parent) override;
|
||||
|
||||
bool attempted = false;
|
||||
bool m_loading = false;
|
||||
bool m_searching = false;
|
||||
QString nextBatch;
|
||||
|
||||
QList<Quotient::PublicRoomsChunk> rooms;
|
||||
@@ -139,8 +141,7 @@ private:
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void serverChanged();
|
||||
void keywordChanged();
|
||||
void searchTextChanged();
|
||||
void showOnlySpacesChanged();
|
||||
void hasMoreChanged();
|
||||
void loadingChanged();
|
||||
void searchingChanged();
|
||||
};
|
||||
|
||||
@@ -23,7 +23,7 @@ ReactionModel::ReactionModel(const Quotient::RoomMessageEvent *event, const NeoC
|
||||
{
|
||||
if (m_event != nullptr && m_room != nullptr) {
|
||||
connect(m_room, &NeoChatRoom::updatedEvent, this, [this](const QString &eventId) {
|
||||
if (m_event && m_event->id() == eventId) {
|
||||
if (m_event->id() == eventId) {
|
||||
updateReactions();
|
||||
}
|
||||
});
|
||||
@@ -80,7 +80,7 @@ QVariant ReactionModel::data(const QModelIndex &index, int role) const
|
||||
"%2 reacted with %3",
|
||||
reaction.authors.count(),
|
||||
text,
|
||||
m_shortcodes.contains(reaction.reaction) ? m_shortcodes[reaction.reaction] : reactionText(reaction.reaction));
|
||||
reactionText(reaction.reaction));
|
||||
return text;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,6 @@ void ReactionModel::updateReactions()
|
||||
beginResetModel();
|
||||
|
||||
m_reactions.clear();
|
||||
m_shortcodes.clear();
|
||||
|
||||
const auto &annotations = m_room->relatedEvents(*m_event, Quotient::EventRelation::AnnotationType);
|
||||
if (annotations.isEmpty()) {
|
||||
@@ -126,9 +125,6 @@ void ReactionModel::updateReactions()
|
||||
}
|
||||
if (const auto &e = eventCast<const Quotient::ReactionEvent>(a)) {
|
||||
reactions[e->key()].append(m_room->user(e->senderId()));
|
||||
if (e->contentJson()[QStringLiteral("shortcode")].toString().length()) {
|
||||
m_shortcodes[e->key()] = e->contentJson()[QStringLiteral("shortcode")].toString().toHtmlEscaped();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,18 +158,8 @@ QHash<int, QByteArray> ReactionModel::roleNames() const
|
||||
};
|
||||
}
|
||||
|
||||
QString ReactionModel::reactionText(QString text) const
|
||||
QString ReactionModel::reactionText(const QString &text)
|
||||
{
|
||||
text = text.toHtmlEscaped();
|
||||
if (text.startsWith(QStringLiteral("mxc://"))) {
|
||||
static QFont font;
|
||||
static int size = font.pixelSize();
|
||||
if (size == -1) {
|
||||
size = font.pointSizeF() * 1.333;
|
||||
}
|
||||
return QStringLiteral("<img src=\"%1\" width=\"%2\" height=\"%2\">")
|
||||
.arg(m_room->connection()->makeMediaUrl(QUrl(text)).toString(), QString::number(size));
|
||||
}
|
||||
const auto isEmoji = [](const QString &text) {
|
||||
#ifdef HAVE_ICU
|
||||
QTextBoundaryFinder finder(QTextBoundaryFinder::Grapheme, text);
|
||||
|
||||
@@ -71,9 +71,8 @@ private:
|
||||
const NeoChatRoom *m_room;
|
||||
const Quotient::RoomMessageEvent *m_event;
|
||||
QList<Reaction> m_reactions;
|
||||
QMap<QString, QString> m_shortcodes;
|
||||
|
||||
void updateReactions();
|
||||
QString reactionText(QString text) const;
|
||||
static QString reactionText(const QString &text);
|
||||
};
|
||||
Q_DECLARE_METATYPE(ReactionModel *)
|
||||
|
||||
@@ -368,6 +368,9 @@ QVariant RoomListModel::data(const QModelIndex &index, int role) const
|
||||
if (role == ReplacementIdRole) {
|
||||
return room->successorId();
|
||||
}
|
||||
if (role == IsDirectChat) {
|
||||
return room->isDirectChat();
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
@@ -401,6 +404,7 @@ QHash<int, QByteArray> RoomListModel::roleNames() const
|
||||
roles[IsSpaceRole] = "isSpace";
|
||||
roles[RoomIdRole] = "roomId";
|
||||
roles[IsChildSpaceRole] = "isChildSpace";
|
||||
roles[IsDirectChat] = "isDirectChat";
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -412,7 +416,7 @@ QString RoomListModel::categoryName(int category)
|
||||
case NeoChatRoomType::Favorite:
|
||||
return i18n("Favorite");
|
||||
case NeoChatRoomType::Direct:
|
||||
return i18n("Direct Messages");
|
||||
return i18n("Friends");
|
||||
case NeoChatRoomType::Normal:
|
||||
return i18n("Normal");
|
||||
case NeoChatRoomType::Deprioritized:
|
||||
|
||||
@@ -77,6 +77,7 @@ public:
|
||||
IsSpaceRole, /**< Whether the room is a space. */
|
||||
IsChildSpaceRole, /**< Whether this space is a child of a different space. */
|
||||
ReplacementIdRole, /**< The room id of the room replacing this one, if any. */
|
||||
IsDirectChat, /**< Whether this room is a direct chat. */
|
||||
};
|
||||
Q_ENUM(EventRoles)
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ void SearchModel::setSearchText(const QString &searchText)
|
||||
|
||||
void SearchModel::search()
|
||||
{
|
||||
Q_ASSERT(m_connection);
|
||||
Q_ASSERT(m_room);
|
||||
setSearching(true);
|
||||
if (m_job) {
|
||||
m_job->abandon();
|
||||
@@ -62,7 +62,7 @@ void SearchModel::search()
|
||||
|
||||
};
|
||||
|
||||
auto job = m_connection->callApi<SearchJob>(SearchJob::Categories{criteria});
|
||||
auto job = m_room->connection()->callApi<SearchJob>(SearchJob::Categories{criteria});
|
||||
m_job = job;
|
||||
connect(job, &BaseJob::finished, this, [this, job] {
|
||||
beginResetModel();
|
||||
@@ -74,17 +74,6 @@ void SearchModel::search()
|
||||
});
|
||||
}
|
||||
|
||||
Connection *SearchModel::connection() const
|
||||
{
|
||||
return m_connection;
|
||||
}
|
||||
|
||||
void SearchModel::setConnection(Connection *connection)
|
||||
{
|
||||
m_connection = connection;
|
||||
Q_EMIT connectionChanged();
|
||||
}
|
||||
|
||||
QVariant SearchModel::data(const QModelIndex &index, int role) const
|
||||
{
|
||||
auto row = index.row();
|
||||
|
||||
@@ -31,11 +31,6 @@ class SearchModel : public QAbstractListModel
|
||||
*/
|
||||
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
|
||||
|
||||
/**
|
||||
* @brief The current connection that the model is using to search for messages.
|
||||
*/
|
||||
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The current room that the search is being done from.
|
||||
*/
|
||||
@@ -94,9 +89,6 @@ public:
|
||||
QString searchText() const;
|
||||
void setSearchText(const QString &searchText);
|
||||
|
||||
Quotient::Connection *connection() const;
|
||||
void setConnection(Quotient::Connection *connection);
|
||||
|
||||
NeoChatRoom *room() const;
|
||||
void setRoom(NeoChatRoom *room);
|
||||
|
||||
@@ -130,7 +122,6 @@ public:
|
||||
|
||||
Q_SIGNALS:
|
||||
void searchTextChanged();
|
||||
void connectionChanged();
|
||||
void roomChanged();
|
||||
void searchingChanged();
|
||||
|
||||
@@ -141,7 +132,6 @@ private:
|
||||
void setSearching(bool searching);
|
||||
|
||||
QString m_searchText;
|
||||
Quotient::Connection *m_connection = nullptr;
|
||||
NeoChatRoom *m_room = nullptr;
|
||||
Quotient::Omittable<Quotient::SearchJob::ResultRoomEvents> m_result = Quotient::none;
|
||||
Quotient::SearchJob *m_job = nullptr;
|
||||
|
||||
@@ -83,6 +83,21 @@ bool SortFilterRoomListModel::filterAcceptsRow(int source_row, const QModelIndex
|
||||
{
|
||||
Q_UNUSED(source_parent);
|
||||
|
||||
bool acceptRoom =
|
||||
sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() == false;
|
||||
|
||||
bool isDirectChat = sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsDirectChat).toBool();
|
||||
// In `show direct chats` mode we only care about whether or not it's a direct chat or if the filter string matches.'
|
||||
if (m_mode == DirectChats) {
|
||||
return isDirectChat && acceptRoom;
|
||||
}
|
||||
|
||||
// When not in `show direct chats` mode, filter them out.
|
||||
if (isDirectChat && m_mode == Rooms) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::JoinStateRole).toString() == QStringLiteral("upgraded")
|
||||
&& dynamic_cast<RoomListModel *>(sourceModel())
|
||||
->connection()
|
||||
@@ -90,10 +105,6 @@ bool SortFilterRoomListModel::filterAcceptsRow(int source_row, const QModelIndex
|
||||
return false;
|
||||
}
|
||||
|
||||
bool acceptRoom =
|
||||
sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::DisplayNameRole).toString().contains(m_filterText, Qt::CaseInsensitive)
|
||||
&& sourceModel()->data(sourceModel()->index(source_row, 0), RoomListModel::IsSpaceRole).toBool() == false;
|
||||
|
||||
if (m_activeSpaceId.isEmpty()) {
|
||||
return acceptRoom;
|
||||
} else {
|
||||
@@ -116,4 +127,20 @@ void SortFilterRoomListModel::setActiveSpaceId(const QString &spaceId)
|
||||
invalidate();
|
||||
}
|
||||
|
||||
SortFilterRoomListModel::Mode SortFilterRoomListModel::mode() const
|
||||
{
|
||||
return m_mode;
|
||||
}
|
||||
|
||||
void SortFilterRoomListModel::setMode(SortFilterRoomListModel::Mode mode)
|
||||
{
|
||||
if (m_mode == mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_mode = mode;
|
||||
Q_EMIT modeChanged();
|
||||
invalidate();
|
||||
}
|
||||
|
||||
#include "moc_sortfilterroomlistmodel.cpp"
|
||||
|
||||
@@ -47,6 +47,11 @@ class SortFilterRoomListModel : public QSortFilterProxyModel
|
||||
*/
|
||||
Q_PROPERTY(QString activeSpaceId READ activeSpaceId WRITE setActiveSpaceId NOTIFY activeSpaceIdChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether only direct chats should be shown.
|
||||
*/
|
||||
Q_PROPERTY(Mode mode READ mode WRITE setMode NOTIFY modeChanged)
|
||||
|
||||
public:
|
||||
enum RoomSortOrder {
|
||||
Alphabetical,
|
||||
@@ -55,6 +60,13 @@ public:
|
||||
};
|
||||
Q_ENUM(RoomSortOrder)
|
||||
|
||||
enum Mode {
|
||||
Rooms,
|
||||
DirectChats,
|
||||
All,
|
||||
};
|
||||
Q_ENUM(Mode)
|
||||
|
||||
explicit SortFilterRoomListModel(QObject *parent = nullptr);
|
||||
|
||||
void setRoomSortOrder(RoomSortOrder sortOrder);
|
||||
@@ -66,6 +78,9 @@ public:
|
||||
QString activeSpaceId() const;
|
||||
void setActiveSpaceId(const QString &spaceId);
|
||||
|
||||
Mode mode() const;
|
||||
void setMode(Mode mode);
|
||||
|
||||
protected:
|
||||
/**
|
||||
* @brief Returns true if the value of source_left is less than source_right.
|
||||
@@ -85,9 +100,11 @@ Q_SIGNALS:
|
||||
void roomSortOrderChanged();
|
||||
void filterTextChanged();
|
||||
void activeSpaceIdChanged();
|
||||
void modeChanged();
|
||||
|
||||
private:
|
||||
RoomSortOrder m_sortOrder = Categories;
|
||||
Mode m_mode = All;
|
||||
QString m_filterText;
|
||||
QString m_activeSpaceId;
|
||||
};
|
||||
|
||||
@@ -26,7 +26,6 @@ void UserDirectoryListModel::setConnection(Connection *conn)
|
||||
|
||||
beginResetModel();
|
||||
|
||||
m_limited = false;
|
||||
attempted = false;
|
||||
users.clear();
|
||||
|
||||
@@ -37,53 +36,50 @@ void UserDirectoryListModel::setConnection(Connection *conn)
|
||||
endResetModel();
|
||||
|
||||
m_connection = conn;
|
||||
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
}
|
||||
|
||||
Q_EMIT connectionChanged();
|
||||
Q_EMIT limitedChanged();
|
||||
|
||||
if (m_job) {
|
||||
m_job->abandon();
|
||||
m_job = nullptr;
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
}
|
||||
|
||||
QString UserDirectoryListModel::keyword() const
|
||||
QString UserDirectoryListModel::searchText() const
|
||||
{
|
||||
return m_keyword;
|
||||
return m_searchText;
|
||||
}
|
||||
|
||||
void UserDirectoryListModel::setKeyword(const QString &value)
|
||||
void UserDirectoryListModel::setSearchText(const QString &value)
|
||||
{
|
||||
if (m_keyword == value) {
|
||||
if (m_searchText == value) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_keyword = value;
|
||||
m_searchText = value;
|
||||
Q_EMIT searchTextChanged();
|
||||
|
||||
if (m_job) {
|
||||
m_job->abandon();
|
||||
m_job = nullptr;
|
||||
Q_EMIT searchingChanged();
|
||||
}
|
||||
|
||||
m_limited = false;
|
||||
attempted = false;
|
||||
|
||||
if (job) {
|
||||
job->abandon();
|
||||
job = nullptr;
|
||||
}
|
||||
|
||||
Q_EMIT keywordChanged();
|
||||
Q_EMIT limitedChanged();
|
||||
}
|
||||
|
||||
bool UserDirectoryListModel::limited() const
|
||||
bool UserDirectoryListModel::searching() const
|
||||
{
|
||||
return m_limited;
|
||||
return m_job != nullptr;
|
||||
}
|
||||
|
||||
void UserDirectoryListModel::search(int count)
|
||||
void UserDirectoryListModel::search(int limit)
|
||||
{
|
||||
if (count < 1) {
|
||||
if (limit < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (job) {
|
||||
if (m_job) {
|
||||
qDebug() << "UserDirectoryListModel: Other jobs running, ignore";
|
||||
|
||||
return;
|
||||
@@ -93,25 +89,22 @@ void UserDirectoryListModel::search(int count)
|
||||
return;
|
||||
}
|
||||
|
||||
job = m_connection->callApi<SearchUserDirectoryJob>(m_keyword, count);
|
||||
m_job = m_connection->callApi<SearchUserDirectoryJob>(m_searchText, limit);
|
||||
Q_EMIT searchingChanged();
|
||||
|
||||
connect(job, &BaseJob::finished, this, [this] {
|
||||
connect(m_job, &BaseJob::finished, this, [this] {
|
||||
attempted = true;
|
||||
|
||||
if (job->status() == BaseJob::Success) {
|
||||
auto users = job->results();
|
||||
if (m_job->status() == BaseJob::Success) {
|
||||
auto users = m_job->results();
|
||||
|
||||
this->beginResetModel();
|
||||
|
||||
this->users = users;
|
||||
this->m_limited = job->limited();
|
||||
|
||||
this->endResetModel();
|
||||
}
|
||||
|
||||
this->job = nullptr;
|
||||
|
||||
Q_EMIT limitedChanged();
|
||||
this->m_job = nullptr;
|
||||
Q_EMIT searchingChanged();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -127,7 +120,7 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
|
||||
return {};
|
||||
}
|
||||
auto user = users.at(index.row());
|
||||
if (role == NameRole) {
|
||||
if (role == DisplayNameRole) {
|
||||
auto displayName = user.displayName;
|
||||
if (!displayName.isEmpty()) {
|
||||
return displayName;
|
||||
@@ -142,18 +135,17 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
|
||||
}
|
||||
if (role == AvatarRole) {
|
||||
auto avatarUrl = user.avatarUrl;
|
||||
|
||||
if (avatarUrl.isEmpty()) {
|
||||
return QString();
|
||||
if (avatarUrl.isEmpty() || !m_connection) {
|
||||
return QUrl();
|
||||
}
|
||||
return avatarUrl.url().remove(0, 6);
|
||||
return m_connection->makeMediaUrl(avatarUrl);
|
||||
}
|
||||
if (role == UserIDRole) {
|
||||
return user.userId;
|
||||
}
|
||||
if (role == DirectChatsRole) {
|
||||
if (role == DirectChatExistsRole) {
|
||||
if (!m_connection) {
|
||||
return QStringList();
|
||||
return false;
|
||||
};
|
||||
|
||||
auto userObj = m_connection->user(user.userId);
|
||||
@@ -162,11 +154,11 @@ QVariant UserDirectoryListModel::data(const QModelIndex &index, int role) const
|
||||
if (userObj && directChats.contains(userObj)) {
|
||||
auto directChatsForUser = directChats.values(userObj);
|
||||
if (!directChatsForUser.isEmpty()) {
|
||||
return QVariant::fromValue(directChatsForUser);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return QStringList();
|
||||
return false;
|
||||
}
|
||||
|
||||
return {};
|
||||
@@ -176,10 +168,10 @@ QHash<int, QByteArray> UserDirectoryListModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> roles;
|
||||
|
||||
roles[NameRole] = "name";
|
||||
roles[AvatarRole] = "avatar";
|
||||
roles[UserIDRole] = "userID";
|
||||
roles[DirectChatsRole] = "directChats";
|
||||
roles[DisplayNameRole] = "displayName";
|
||||
roles[AvatarRole] = "avatarUrl";
|
||||
roles[UserIDRole] = "userId";
|
||||
roles[DirectChatExistsRole] = "directChatExists";
|
||||
|
||||
return roles;
|
||||
}
|
||||
|
||||
@@ -35,24 +35,24 @@ class UserDirectoryListModel : public QAbstractListModel
|
||||
Q_PROPERTY(Quotient::Connection *connection READ connection WRITE setConnection NOTIFY connectionChanged)
|
||||
|
||||
/**
|
||||
* @brief The keyword to use in the search.
|
||||
* @brief The text to search the public room list for.
|
||||
*/
|
||||
Q_PROPERTY(QString keyword READ keyword WRITE setKeyword NOTIFY keywordChanged)
|
||||
Q_PROPERTY(QString searchText READ searchText WRITE setSearchText NOTIFY searchTextChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether the current results have been truncated.
|
||||
* @brief Whether the model is searching.
|
||||
*/
|
||||
Q_PROPERTY(bool limited READ limited NOTIFY limitedChanged)
|
||||
Q_PROPERTY(bool searching READ searching NOTIFY searchingChanged)
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Defines the model roles.
|
||||
*/
|
||||
enum EventRoles {
|
||||
NameRole = Qt::DisplayRole + 1, /**< The user's display name. */
|
||||
DisplayNameRole = Qt::DisplayRole, /**< The user's display name. */
|
||||
AvatarRole, /**< The source URL for the user's avatar. */
|
||||
UserIDRole, /**< Matrix ID of the user. */
|
||||
DirectChatsRole, /**< A list of direct chat matrix IDs with the user. */
|
||||
DirectChatExistsRole, /**< Whether there is already a direct chat with the user. */
|
||||
};
|
||||
|
||||
explicit UserDirectoryListModel(QObject *parent = nullptr);
|
||||
@@ -60,17 +60,17 @@ public:
|
||||
[[nodiscard]] Quotient::Connection *connection() const;
|
||||
void setConnection(Quotient::Connection *conn);
|
||||
|
||||
[[nodiscard]] QString keyword() const;
|
||||
void setKeyword(const QString &value);
|
||||
[[nodiscard]] QString searchText() const;
|
||||
void setSearchText(const QString &searchText);
|
||||
|
||||
[[nodiscard]] bool limited() const;
|
||||
[[nodiscard]] bool searching() const;
|
||||
|
||||
/**
|
||||
* @brief Get the given role value at the given index.
|
||||
*
|
||||
* @sa QAbstractItemModel::data
|
||||
*/
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = NameRole) const override;
|
||||
[[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
|
||||
|
||||
/**
|
||||
* @brief Number of rows in the model.
|
||||
@@ -87,23 +87,23 @@ public:
|
||||
[[nodiscard]] QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
/**
|
||||
* @brief Start the user search.
|
||||
* @brief Search the user directory.
|
||||
*
|
||||
* @param limit the maximum number of rooms to load.
|
||||
*/
|
||||
Q_INVOKABLE void search(int count = 50);
|
||||
Q_INVOKABLE void search(int limit = 50);
|
||||
|
||||
Q_SIGNALS:
|
||||
void connectionChanged();
|
||||
void keywordChanged();
|
||||
void limitedChanged();
|
||||
void searchTextChanged();
|
||||
void searchingChanged();
|
||||
|
||||
private:
|
||||
Quotient::Connection *m_connection = nullptr;
|
||||
QString m_keyword;
|
||||
bool m_limited = false;
|
||||
QString m_searchText;
|
||||
|
||||
bool attempted = false;
|
||||
|
||||
QList<Quotient::SearchUserDirectoryJob::User> users;
|
||||
|
||||
Quotient::SearchUserDirectoryJob *job = nullptr;
|
||||
Quotient::SearchUserDirectoryJob *m_job = nullptr;
|
||||
};
|
||||
|
||||
@@ -76,7 +76,6 @@ Comment[ru]=Клиент для Matrix — децентрализованног
|
||||
Comment[sk]=Klient pre matrix, decentralizovaný komunikačný protokol
|
||||
Comment[sl]=Odjemalec za decentralizirani komunikacijski protokol matrix
|
||||
Comment[sv]=En klient för matrix, det decentraliserade kommunikationsprotokollet
|
||||
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
|
||||
@@ -189,6 +188,7 @@ Name[ie]=Nov invitation
|
||||
Name[it]=Nuovo invito
|
||||
Name[ka]=ახალი მოსაწვევი
|
||||
Name[ko]=새 초대장
|
||||
Name[lt]=Naujas pakvietimas
|
||||
Name[nl]=Nieuwe uitnodiging
|
||||
Name[nn]=Ny invitasjon
|
||||
Name[pa]=ਨਵਾਂ ਸੱਦਾ
|
||||
@@ -226,6 +226,7 @@ Comment[ie]=Vu have un nov invitation a un chambre
|
||||
Comment[it]=È presente un nuovo invito a una stanza
|
||||
Comment[ka]=გაქვთ ახალი ოთახის მოსაწვევი
|
||||
Comment[ko]=새로운 대화방 초대장을 받음
|
||||
Comment[lt]=Yra naujas pakvietimas į kambarį
|
||||
Comment[nl]=Er is een nieuwe uitnodiging naar een room
|
||||
Comment[nn]=Du har ein ny invitasjon til eit rom
|
||||
Comment[pa]=ਰੂਮ ਲਈ ਨਵਾਂ ਸੱਦਾ ਹੈ
|
||||
|
||||
@@ -22,10 +22,6 @@
|
||||
<label>Background transparency value</label>
|
||||
<default>0.3</default>
|
||||
</entry>
|
||||
<entry name="MergeRoomList" type="bool">
|
||||
<label>Merge Room Lists</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
<entry name="AllowQuickEdit" type="bool">
|
||||
<label>Use s/text/replacement syntax to edit your last message.</label>
|
||||
<default>false</default>
|
||||
@@ -156,11 +152,5 @@
|
||||
<default></default>
|
||||
</entry>
|
||||
</group>
|
||||
<group name="FeatureFlags">
|
||||
<entry name="Threads" type="bool">
|
||||
<label>Enable threads</label>
|
||||
<default>false</default>
|
||||
</entry>
|
||||
</group>
|
||||
</kcfg>
|
||||
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "jobs/neochatdeactivateaccountjob.h"
|
||||
#include "roommanager.h"
|
||||
|
||||
#include <Quotient/connection.h>
|
||||
#include <Quotient/quotient_common.h>
|
||||
#include <qt6keychain/keychain.h>
|
||||
|
||||
#include <KLocalizedString>
|
||||
@@ -17,7 +19,9 @@
|
||||
#include <Quotient/csapi/content-repo.h>
|
||||
#include <Quotient/csapi/profile.h>
|
||||
#include <Quotient/database.h>
|
||||
#include <Quotient/jobs/downloadfilejob.h>
|
||||
#include <Quotient/qt_connection_util.h>
|
||||
#include <Quotient/room.h>
|
||||
#include <Quotient/settings.h>
|
||||
#include <Quotient/user.h>
|
||||
|
||||
@@ -32,6 +36,17 @@ using namespace Qt::StringLiterals;
|
||||
|
||||
NeoChatConnection::NeoChatConnection(QObject *parent)
|
||||
: Connection(parent)
|
||||
{
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
||||
: Connection(server, parent)
|
||||
{
|
||||
connectSignals();
|
||||
}
|
||||
|
||||
void NeoChatConnection::connectSignals()
|
||||
{
|
||||
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
@@ -49,14 +64,37 @@ NeoChatConnection::NeoChatConnection(QObject *parent)
|
||||
Q_EMIT userConsentRequired(job->errorUrl());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
NeoChatConnection::NeoChatConnection(const QUrl &server, QObject *parent)
|
||||
: Connection(server, parent)
|
||||
{
|
||||
connect(this, &NeoChatConnection::accountDataChanged, this, [this](const QString &type) {
|
||||
if (type == QLatin1String("org.kde.neochat.account_label")) {
|
||||
Q_EMIT labelChanged();
|
||||
connect(this, &NeoChatConnection::requestFailed, this, [](BaseJob *job) {
|
||||
if (dynamic_cast<DownloadFileJob *>(job) && job->jsonData()["errcode"_ls].toString() == "M_TOO_LARGE"_ls) {
|
||||
RoomManager::instance().warning(i18n("File too large to download."), i18n("Contact your matrix server administrator for support."));
|
||||
}
|
||||
});
|
||||
connect(this, &NeoChatConnection::directChatsListChanged, this, [this](DirectChatsMap additions, DirectChatsMap removals) {
|
||||
Q_EMIT directChatInvitesChanged();
|
||||
for (const auto &chatId : additions) {
|
||||
if (const auto chat = room(chatId)) {
|
||||
connect(chat, &Room::unreadStatsChanged, this, [this]() {
|
||||
Q_EMIT directChatNotificationsChanged();
|
||||
});
|
||||
}
|
||||
}
|
||||
for (const auto &chatId : removals) {
|
||||
if (const auto chat = room(chatId)) {
|
||||
disconnect(chat, &Room::unreadStatsChanged, this, nullptr);
|
||||
}
|
||||
}
|
||||
});
|
||||
connect(this, &NeoChatConnection::joinedRoom, this, [this](Room *room) {
|
||||
if (room->isDirectChat()) {
|
||||
connect(room, &Room::unreadStatsChanged, this, [this]() {
|
||||
Q_EMIT directChatNotificationsChanged();
|
||||
});
|
||||
}
|
||||
});
|
||||
connect(this, &NeoChatConnection::leftRoom, this, [this](Room *room, Room *prev) {
|
||||
Q_UNUSED(room)
|
||||
if (prev && prev->isDirectChat()) {
|
||||
Q_EMIT directChatInvitesChanged();
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -238,6 +276,20 @@ void NeoChatConnection::createSpace(const QString &name, const QString &topic, c
|
||||
});
|
||||
}
|
||||
|
||||
bool NeoChatConnection::directChatExists(Quotient::User *user)
|
||||
{
|
||||
return directChats().contains(user);
|
||||
}
|
||||
|
||||
void NeoChatConnection::openOrCreateDirectChat(const QString &userId)
|
||||
{
|
||||
if (auto user = this->user(userId)) {
|
||||
openOrCreateDirectChat(user);
|
||||
} else {
|
||||
qWarning() << "openOrCreateDirectChat: Couldn't get user object for ID " << userId << ", unable to open/request direct chat.";
|
||||
}
|
||||
}
|
||||
|
||||
void NeoChatConnection::openOrCreateDirectChat(User *user)
|
||||
{
|
||||
const auto existing = directChats();
|
||||
@@ -252,6 +304,32 @@ void NeoChatConnection::openOrCreateDirectChat(User *user)
|
||||
requestDirectChat(user);
|
||||
}
|
||||
|
||||
qsizetype NeoChatConnection::directChatNotifications() const
|
||||
{
|
||||
qsizetype notifications = 0;
|
||||
QStringList added; // The same ID can be in the list multiple times.
|
||||
for (const auto &chatId : directChats()) {
|
||||
if (!added.contains(chatId)) {
|
||||
if (const auto chat = room(chatId)) {
|
||||
notifications += chat->notificationCount();
|
||||
added += chatId;
|
||||
}
|
||||
}
|
||||
}
|
||||
return notifications;
|
||||
}
|
||||
|
||||
bool NeoChatConnection::directChatInvites() const
|
||||
{
|
||||
auto inviteRooms = rooms(JoinState::Invite);
|
||||
for (const auto inviteRoom : inviteRooms) {
|
||||
if (inviteRoom->isDirectChat()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
QCoro::Task<void> NeoChatConnection::setupPushNotifications(QString endpoint)
|
||||
{
|
||||
#ifdef HAVE_KUNIFIEDPUSH
|
||||
|
||||
@@ -27,6 +27,16 @@ class NeoChatConnection : public Quotient::Connection
|
||||
Q_PROPERTY(QString deviceKey READ deviceKey CONSTANT)
|
||||
Q_PROPERTY(QString encryptionKey READ encryptionKey CONSTANT)
|
||||
|
||||
/**
|
||||
* @brief The total number of notifications for all direct chats.
|
||||
*/
|
||||
Q_PROPERTY(qsizetype directChatNotifications READ directChatNotifications NOTIFY directChatNotificationsChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether there is at least one invite to a direct chat.
|
||||
*/
|
||||
Q_PROPERTY(bool directChatInvites READ directChatInvites NOTIFY directChatInvitesChanged)
|
||||
|
||||
/**
|
||||
* @brief Whether NeoChat is currently able to connect to the server.
|
||||
*/
|
||||
@@ -80,12 +90,27 @@ public:
|
||||
Q_INVOKABLE void createSpace(const QString &name, const QString &topic, const QString &parent = {}, bool setChildParent = false);
|
||||
|
||||
/**
|
||||
* @brief Join a direct chat with the given user.
|
||||
* @brief Whether a direct chat with the user exists.
|
||||
*/
|
||||
Q_INVOKABLE bool directChatExists(Quotient::User *user);
|
||||
|
||||
/**
|
||||
* @brief Join a direct chat with the given user ID.
|
||||
*
|
||||
* If a direct chat with the user doesn't exist one is created and then joined.
|
||||
*/
|
||||
Q_INVOKABLE void openOrCreateDirectChat(const QString &userId);
|
||||
|
||||
/**
|
||||
* @brief Join a direct chat with the given user object.
|
||||
*
|
||||
* If a direct chat with the user doesn't exist one is created and then joined.
|
||||
*/
|
||||
Q_INVOKABLE void openOrCreateDirectChat(Quotient::User *user);
|
||||
|
||||
qsizetype directChatNotifications() const;
|
||||
bool directChatInvites() const;
|
||||
|
||||
// note: this is intentionally a copied QString because
|
||||
// the reference could be destroyed before the task is finished
|
||||
QCoro::Task<void> setupPushNotifications(QString endpoint);
|
||||
@@ -97,6 +122,8 @@ public:
|
||||
|
||||
Q_SIGNALS:
|
||||
void labelChanged();
|
||||
void directChatNotificationsChanged();
|
||||
void directChatInvitesChanged();
|
||||
void isOnlineChanged();
|
||||
void passwordStatus(NeoChatConnection::PasswordStatus status);
|
||||
void userConsentRequired(QUrl url);
|
||||
@@ -104,4 +131,6 @@ Q_SIGNALS:
|
||||
private:
|
||||
bool m_isOnline = true;
|
||||
void setIsOnline(bool isOnline);
|
||||
|
||||
void connectSignals();
|
||||
};
|
||||
|
||||
@@ -1826,7 +1826,11 @@ int NeoChatRoom::maxRoomVersion() const
|
||||
|
||||
Quotient::User *NeoChatRoom::directChatRemoteUser() const
|
||||
{
|
||||
return connection()->directChatUsers(this)[0];
|
||||
auto users = connection()->directChatUsers(this);
|
||||
if (users.isEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
return users[0];
|
||||
}
|
||||
|
||||
void NeoChatRoom::sendLocation(float lat, float lon, const QString &description)
|
||||
|
||||
@@ -63,6 +63,7 @@ Comment[ie]=Trovar chambres in NeoChat
|
||||
Comment[it]=Trova stanze in NeoChat
|
||||
Comment[ka]=იპოვე ოთახები NeoChat-ში
|
||||
Comment[ko]=NeoChat에서 대화방 찾기
|
||||
Comment[lt]=Rasti kambarius NeoChat
|
||||
Comment[nl]=Rooms zoeken in NeoChat
|
||||
Comment[nn]=Finn rom i NeoChat
|
||||
Comment[pl]=Znajdź pokoje w NeoChat
|
||||
|
||||
35
src/proxycontroller.cpp
Normal file
35
src/proxycontroller.cpp
Normal file
@@ -0,0 +1,35 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#include "proxycontroller.h"
|
||||
|
||||
#include <QNetworkProxy>
|
||||
|
||||
#include "neochatconfig.h"
|
||||
|
||||
void ProxyController::setApplicationProxy()
|
||||
{
|
||||
auto cfg = NeoChatConfig::self();
|
||||
QNetworkProxy proxy;
|
||||
|
||||
switch (cfg->proxyType()) {
|
||||
case 1:
|
||||
proxy.setType(QNetworkProxy::HttpProxy);
|
||||
break;
|
||||
case 2:
|
||||
proxy.setType(QNetworkProxy::Socks5Proxy);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
proxy.setHostName(cfg->proxyHost());
|
||||
proxy.setPort(cfg->proxyPort());
|
||||
proxy.setUser(cfg->proxyUser());
|
||||
proxy.setPassword(cfg->proxyPassword());
|
||||
QNetworkProxy::setApplicationProxy(proxy);
|
||||
}
|
||||
|
||||
ProxyController::ProxyController(QObject *parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
36
src/proxycontroller.h
Normal file
36
src/proxycontroller.h
Normal file
@@ -0,0 +1,36 @@
|
||||
// SPDX-FileCopyrightText: 2024 Tobias Fella <tobias.fella@kde.org>
|
||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QQmlEngine>
|
||||
|
||||
class ProxyController : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
QML_ELEMENT
|
||||
QML_SINGLETON
|
||||
|
||||
public:
|
||||
static ProxyController &instance()
|
||||
{
|
||||
static ProxyController _instance;
|
||||
return _instance;
|
||||
}
|
||||
static ProxyController *create(QQmlEngine *engine, QJSEngine *)
|
||||
{
|
||||
engine->setObjectOwnership(&instance(), QQmlEngine::CppOwnership);
|
||||
return &instance();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the QNetworkProxy for the application.
|
||||
*
|
||||
* @sa QNetworkProxy::setApplicationProxy
|
||||
*/
|
||||
Q_INVOKABLE void setApplicationProxy();
|
||||
|
||||
private:
|
||||
explicit ProxyController(QObject *parent = nullptr);
|
||||
};
|
||||
@@ -42,8 +42,7 @@ MessageDelegate {
|
||||
bubbleContent: ColumnLayout {
|
||||
MediaPlayer {
|
||||
id: audio
|
||||
onErrorOccurred: (error, errorString) => console.warn("Audio playback error:" + error + errorString)
|
||||
audioOutput: AudioOutput {}
|
||||
source: root.progressInfo.localPath
|
||||
}
|
||||
|
||||
states: [
|
||||
@@ -54,7 +53,7 @@ MessageDelegate {
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: currentRoom.downloadFile(root.eventId)
|
||||
onClicked: root.room.downloadFile(root.eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
@@ -68,25 +67,24 @@ MessageDelegate {
|
||||
target: playButton
|
||||
icon.name: "media-playback-stop"
|
||||
onClicked: {
|
||||
currentRoom.cancelFileTransfer(root.eventId)
|
||||
root.room.cancelFileTransfer(root.eventId)
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "paused"
|
||||
when: root.progressInfo.completed && (audio.playbackState === MediaPlayer.StoppedState || audio.playbackState === MediaPlayer.PausedState)
|
||||
when: root.progressInfo.completed && (audio.playbackState === Audio.StoppedState || audio.playbackState === Audio.PausedState)
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
icon.name: "media-playback-start"
|
||||
onClicked: {
|
||||
audio.source = root.progressInfo.localPath;
|
||||
audio.play();
|
||||
audio.play()
|
||||
}
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "playing"
|
||||
when: root.progressInfo.completed && audio.playbackState === MediaPlayer.PlayingState
|
||||
when: root.progressInfo.completed && audio.playbackState === Audio.PlayingState
|
||||
|
||||
PropertyChanges {
|
||||
target: playButton
|
||||
|
||||
@@ -13,7 +13,7 @@ import org.kde.kirigamiaddons.labs.components as KirigamiComponents
|
||||
Delegates.RoundedItemDelegate {
|
||||
id: root
|
||||
|
||||
required property url source
|
||||
property url source
|
||||
|
||||
signal contextMenuRequested()
|
||||
signal selected()
|
||||
|
||||
@@ -220,7 +220,7 @@ QQC2.Control {
|
||||
x: textField.cursorRectangle.x
|
||||
y: textField.cursorRectangle.y - height
|
||||
|
||||
onFormattingSelected: _private.formatText(format, selectionStart, selectionEnd)
|
||||
onFormattingSelected: root.formatText(format, selectionStart, selectionEnd)
|
||||
}
|
||||
|
||||
Keys.onDeletePressed: {
|
||||
|
||||
@@ -30,7 +30,7 @@ QQC2.ItemDelegate {
|
||||
width: ListView.view.width
|
||||
height: visible ? ListView.view.width : 0
|
||||
|
||||
visible: root.categoryVisible || filterText.length > 0 || Config.mergeRoomList
|
||||
visible: root.categoryVisible || filterText.length > 0
|
||||
|
||||
contentItem: KirigamiComponents.Avatar {
|
||||
source: root.avatar ? `image://mxc/${root.avatar}` : ""
|
||||
|
||||
@@ -70,6 +70,7 @@ Loader {
|
||||
|
||||
QQC2.Menu {
|
||||
title: i18n("Notification State")
|
||||
icon.name: "notifications"
|
||||
|
||||
QQC2.MenuItem {
|
||||
text: i18n("Follow Global Setting")
|
||||
|
||||
@@ -103,7 +103,7 @@ FormCard.FormCardPage {
|
||||
visible: !chosenRoomDelegate.visible
|
||||
text: i18nc("@action:button", "Pick room")
|
||||
onClicked: {
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
chosenRoomDelegate.roomId = roomId;
|
||||
chosenRoomDelegate.displayName = displayName;
|
||||
@@ -182,7 +182,7 @@ FormCard.FormCardPage {
|
||||
}
|
||||
|
||||
onClicked: {
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
chosenRoomDelegate.roomId = roomId;
|
||||
chosenRoomDelegate.displayName = displayName;
|
||||
|
||||
@@ -11,9 +11,12 @@ import org.kde.neochat
|
||||
DelegateChooser {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The NeoChatRoom the delegate is being displayed in.
|
||||
*/
|
||||
required property NeoChatRoom room
|
||||
|
||||
role: "delegateType"
|
||||
property var room
|
||||
required property NeoChatConnection connection
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.State
|
||||
@@ -23,63 +26,63 @@ DelegateChooser {
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Emote
|
||||
delegate: TextDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Message
|
||||
delegate: TextDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Notice
|
||||
delegate: TextDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Image
|
||||
delegate: ImageDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Sticker
|
||||
delegate: ImageDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Audio
|
||||
delegate: AudioDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Video
|
||||
delegate: VideoDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.File
|
||||
delegate: FileDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Encrypted
|
||||
delegate: EncryptedDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
@@ -91,14 +94,14 @@ DelegateChooser {
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Poll
|
||||
delegate: PollDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
DelegateChoice {
|
||||
roleValue: DelegateType.Location
|
||||
delegate: LocationDelegate {
|
||||
connection: root.connection
|
||||
room: root.room
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +109,6 @@ DelegateChooser {
|
||||
roleValue: DelegateType.LiveLocation
|
||||
delegate: LiveLocationDelegate {
|
||||
room: root.room
|
||||
connection: root.connection
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,7 +21,7 @@ RowLayout {
|
||||
text: i18n("Explore rooms")
|
||||
icon.name: "compass"
|
||||
onTriggered: {
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
if (isJoined) {
|
||||
RoomManager.enterRoom(root.connection.room(roomId))
|
||||
@@ -32,9 +32,9 @@ RowLayout {
|
||||
}
|
||||
}
|
||||
property Kirigami.Action chatAction: Kirigami.Action {
|
||||
text: i18n("Start a Chat")
|
||||
text: i18n("Find your friends")
|
||||
icon.name: "list-add-user"
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection}, {title: i18nc("@title", "Start a Chat")})
|
||||
onTriggered: pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")})
|
||||
}
|
||||
property Kirigami.Action roomAction: Kirigami.Action {
|
||||
text: i18n("Create a Room")
|
||||
|
||||
@@ -52,7 +52,7 @@ ColumnLayout {
|
||||
text: i18n("Explore rooms")
|
||||
icon.name: "compass"
|
||||
onTriggered: {
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
if (isJoined) {
|
||||
RoomManager.enterRoom(root.connection.room(roomId));
|
||||
@@ -64,10 +64,10 @@ ColumnLayout {
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
text: i18n("Start a Chat")
|
||||
text: i18n("Find your friends")
|
||||
icon.name: "list-add-user"
|
||||
onTriggered: {
|
||||
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection}, {title: i18nc("@title", "Start a Chat")})
|
||||
pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")})
|
||||
exploreTabBar.currentIndex = -1;
|
||||
}
|
||||
},
|
||||
|
||||
112
src/qml/ExploreRoomsPage.qml
Normal file
112
src/qml/ExploreRoomsPage.qml
Normal file
@@ -0,0 +1,112 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.qmlmodels
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
/**
|
||||
* @brief Component for finding rooms for the public list.
|
||||
*
|
||||
* This component is based on a SearchPage, adding the functionality to select or
|
||||
* enter a server in the header, as well as the ability to manually type a room in
|
||||
* if the public room search cannot find it.
|
||||
*
|
||||
* @sa SearchPage
|
||||
*/
|
||||
SearchPage {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The connection for the current local user.
|
||||
*/
|
||||
required property NeoChatConnection connection
|
||||
|
||||
/**
|
||||
* @brief Whether results should only includes spaces.
|
||||
*/
|
||||
property bool showOnlySpaces: false
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when a room is selected.
|
||||
*
|
||||
* The signal contains all the room's info so that it can be acted
|
||||
* upon as required, e.g. joining or entering the room or adding the room as
|
||||
* the child of a space.
|
||||
*/
|
||||
signal roomSelected(string roomId,
|
||||
string displayName,
|
||||
url avatarUrl,
|
||||
string alias,
|
||||
string topic,
|
||||
int memberCount,
|
||||
bool isJoined)
|
||||
|
||||
title: i18nc("@action:title", "Explore Rooms")
|
||||
|
||||
Component.onCompleted: focusSearch()
|
||||
|
||||
headerTrailing: ServerComboBox {
|
||||
id: serverComboBox
|
||||
connection: root.connection
|
||||
}
|
||||
|
||||
model: PublicRoomListModel {
|
||||
id: publicRoomListModel
|
||||
|
||||
connection: root.connection
|
||||
server: serverComboBox.server
|
||||
showOnlySpaces: root.showOnlySpaces
|
||||
}
|
||||
|
||||
modelDelegate: ExplorerDelegate {
|
||||
onRoomSelected: (roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
listHeaderDelegate: Delegates.RoundedItemDelegate {
|
||||
onClicked: _private.openManualRoomDialog()
|
||||
|
||||
text: i18n("Enter a room address")
|
||||
icon.name: "compass"
|
||||
icon.width: Kirigami.Units.gridUnit * 2
|
||||
icon.height: Kirigami.Units.gridUnit * 2
|
||||
}
|
||||
|
||||
listFooterDelegate: QQC2.ProgressBar {
|
||||
width: ListView.view.width
|
||||
leftInset: Kirigami.Units.largeSpacing
|
||||
rightInset: Kirigami.Units.largeSpacing
|
||||
visible: root.count !== 0 && publicRoomListModel.searching
|
||||
indeterminate: true
|
||||
}
|
||||
|
||||
searchFieldPlaceholder: i18n("Find a room…")
|
||||
noResultPlaceholderMessage: i18nc("@info:label", "No public rooms found")
|
||||
|
||||
Component {
|
||||
id: manualRoomDialog
|
||||
ManualRoomDialog {}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _private
|
||||
function openManualRoomDialog() {
|
||||
let dialog = manualRoomDialog.createObject(applicationWindow().overlay, {connection: root.connection});
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
|
||||
root.closeDialog();
|
||||
});
|
||||
dialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.platform
|
||||
import Qt.labs.qmlmodels
|
||||
|
||||
import org.kde.coreaddons
|
||||
import org.kde.kirigami as Kirigami
|
||||
@@ -41,8 +42,11 @@ MessageDelegate {
|
||||
*/
|
||||
property bool autoOpenFile: false
|
||||
|
||||
onDownloadedChanged: if (autoOpenFile) {
|
||||
openSavedFile();
|
||||
onDownloadedChanged: {
|
||||
itineraryModel.path = root.progressInfo.localPath
|
||||
if (autoOpenFile) {
|
||||
openSavedFile();
|
||||
}
|
||||
}
|
||||
|
||||
onOpenContextMenu: RoomManager.viewEventMenu(eventId, author, delegateType, plainText, "", "", mediaInfo.mimeType, progressInfo)
|
||||
@@ -50,142 +54,224 @@ MessageDelegate {
|
||||
function saveFileAs() {
|
||||
const dialog = fileDialog.createObject(QQC2.ApplicationWindow.overlay)
|
||||
dialog.open()
|
||||
dialog.currentFile = dialog.folder + "/" + currentRoom.fileNameToDownload(root.eventId)
|
||||
dialog.currentFile = dialog.folder + "/" + root.room.fileNameToDownload(root.eventId)
|
||||
}
|
||||
|
||||
function openSavedFile() {
|
||||
UrlHelper.openUrl(root.progressInfo.localPath);
|
||||
}
|
||||
|
||||
bubbleContent: RowLayout {
|
||||
bubbleContent: ColumnLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
RowLayout {
|
||||
spacing: Kirigami.Units.largeSpacing
|
||||
|
||||
states: [
|
||||
State {
|
||||
name: "downloadedInstant"
|
||||
when: root.progressInfo.completed && autoOpenFile
|
||||
states: [
|
||||
State {
|
||||
name: "downloadedInstant"
|
||||
when: root.progressInfo.completed && autoOpenFile
|
||||
|
||||
PropertyChanges {
|
||||
target: openButton
|
||||
icon.name: "document-open"
|
||||
onClicked: openSavedFile()
|
||||
PropertyChanges {
|
||||
target: openButton
|
||||
icon.name: "document-open"
|
||||
onClicked: openSavedFile()
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
icon.name: "download"
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
||||
onClicked: saveFileAs()
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloaded"
|
||||
when: root.progressInfo.completed && !autoOpenFile
|
||||
|
||||
PropertyChanges {
|
||||
target: openButton
|
||||
visible: false
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
icon.name: "document-open"
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
||||
onClicked: openSavedFile()
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloading"
|
||||
when: root.progressInfo.active
|
||||
|
||||
PropertyChanges {
|
||||
target: openButton
|
||||
visible: false
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: sizeLabel
|
||||
text: i18nc("file download progress", "%1 / %2", Format.formatByteSize(root.progressInfo.progress), Format.formatByteSize(root.progressInfo.total))
|
||||
}
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
icon.name: "media-playback-stop"
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
|
||||
onClicked: root.room.cancelFileTransfer(root.eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "raw"
|
||||
when: true
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
onClicked: root.saveFileAs()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
icon.name: "download"
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
||||
onClicked: saveFileAs()
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloaded"
|
||||
when: root.progressInfo.completed && !autoOpenFile
|
||||
|
||||
PropertyChanges {
|
||||
target: openButton
|
||||
visible: false
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
icon.name: "document-open"
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
||||
onClicked: openSavedFile()
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "downloading"
|
||||
when: root.progressInfo.active
|
||||
|
||||
PropertyChanges {
|
||||
target: openButton
|
||||
visible: false
|
||||
}
|
||||
|
||||
PropertyChanges {
|
||||
target: sizeLabel
|
||||
text: i18nc("file download progress", "%1 / %2", Format.formatByteSize(root.progressInfo.progress), Format.formatByteSize(root.progressInfo.total))
|
||||
}
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
icon.name: "media-playback-stop"
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; stops downloading the message's file", "Stop Download")
|
||||
onClicked: currentRoom.cancelFileTransfer(root.eventId)
|
||||
}
|
||||
},
|
||||
State {
|
||||
name: "raw"
|
||||
when: true
|
||||
|
||||
PropertyChanges {
|
||||
target: downloadButton
|
||||
onClicked: root.saveFileAs()
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
Kirigami.Icon {
|
||||
source: root.mediaInfo.mimeIcon
|
||||
fallback: "unknown"
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: root.display
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
QQC2.Label {
|
||||
id: sizeLabel
|
||||
Layout.fillWidth: true
|
||||
text: Format.formatByteSize(root.mediaInfo.size)
|
||||
opacity: 0.7
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
id: openButton
|
||||
icon.name: "document-open"
|
||||
onClicked: {
|
||||
autoOpenFile = true;
|
||||
currentRoom.downloadTempFile(root.eventId);
|
||||
Kirigami.Icon {
|
||||
source: root.mediaInfo.mimeIcon
|
||||
fallback: "unknown"
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
ColumnLayout {
|
||||
spacing: 0
|
||||
QQC2.Label {
|
||||
Layout.fillWidth: true
|
||||
text: root.display
|
||||
wrapMode: Text.Wrap
|
||||
elide: Text.ElideRight
|
||||
}
|
||||
QQC2.Label {
|
||||
id: sizeLabel
|
||||
Layout.fillWidth: true
|
||||
text: Format.formatByteSize(root.mediaInfo.size)
|
||||
opacity: 0.7
|
||||
elide: Text.ElideRight
|
||||
maximumLineCount: 1
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
id: downloadButton
|
||||
icon.name: "download"
|
||||
QQC2.Button {
|
||||
id: openButton
|
||||
icon.name: "document-open"
|
||||
onClicked: {
|
||||
autoOpenFile = true;
|
||||
root.room.downloadTempFile(root.eventId);
|
||||
}
|
||||
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to open its downloaded file with an appropriate application", "Open File")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDialog
|
||||
QQC2.Button {
|
||||
id: downloadButton
|
||||
icon.name: "download"
|
||||
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: Config.lastSaveDirectory.length > 0 ? Config.lastSaveDirectory : StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
Config.lastSaveDirectory = folder
|
||||
Config.save()
|
||||
if (autoOpenFile) {
|
||||
UrlHelper.copyTo(root.progressInfo.localPath, file)
|
||||
} else {
|
||||
currentRoom.download(root.eventId, file);
|
||||
QQC2.ToolTip.text: i18nc("tooltip for a button on a message; offers ability to download its file", "Download")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
}
|
||||
|
||||
Component {
|
||||
id: fileDialog
|
||||
|
||||
FileDialog {
|
||||
fileMode: FileDialog.SaveFile
|
||||
folder: Config.lastSaveDirectory.length > 0 ? Config.lastSaveDirectory : StandardPaths.writableLocation(StandardPaths.DownloadLocation)
|
||||
onAccepted: {
|
||||
Config.lastSaveDirectory = folder
|
||||
Config.save()
|
||||
if (autoOpenFile) {
|
||||
UrlHelper.copyTo(root.progressInfo.localPath, file)
|
||||
} else {
|
||||
root.room.download(root.eventId, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Repeater {
|
||||
id: itinerary
|
||||
model: ItineraryModel {
|
||||
id: itineraryModel
|
||||
connection: root.room.connection
|
||||
}
|
||||
delegate: DelegateChooser {
|
||||
role: "type"
|
||||
DelegateChoice {
|
||||
roleValue: "TrainReservation"
|
||||
delegate: ColumnLayout {
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
RowLayout {
|
||||
QQC2.Label {
|
||||
text: model.name
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.coach ? i18n("Coach: %1, Seat: %2", model.coach, model.seat) : ""
|
||||
visible: model.coach
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
ColumnLayout {
|
||||
QQC2.Label {
|
||||
text: model.departureStation + (model.departurePlatform ? (" [" + model.departurePlatform + "]") : "")
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.departureTime
|
||||
opacity: 0.7
|
||||
}
|
||||
}
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
ColumnLayout {
|
||||
QQC2.Label {
|
||||
text: model.arrivalStation + (model.arrivalPlatform ? (" [" + model.arrivalPlatform + "]") : "")
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.arrivalTime
|
||||
opacity: 0.7
|
||||
Layout.alignment: Qt.AlignRight
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
DelegateChoice {
|
||||
roleValue: "LodgingReservation"
|
||||
delegate: ColumnLayout {
|
||||
Kirigami.Separator {
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.name
|
||||
}
|
||||
QQC2.Label {
|
||||
text: i18nc("<start time> - <end time>", "%1 - %2", model.startTime, model.endTime)
|
||||
}
|
||||
QQC2.Label {
|
||||
text: model.address
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
QQC2.Button {
|
||||
icon.name: "map-globe"
|
||||
text: i18nc("@action", "Send to KDE Itinerary")
|
||||
QQC2.ToolTip.visible: hovered
|
||||
QQC2.ToolTip.text: text
|
||||
QQC2.ToolTip.delay: Kirigami.Units.toolTipDelay
|
||||
onClicked: itineraryModel.sendToItinerary()
|
||||
visible: itinerary.count > 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -140,29 +140,6 @@ FormCard.FormCardPage {
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
title: i18n("Rooms and private chats")
|
||||
}
|
||||
FormCard.FormCard {
|
||||
FormCard.FormRadioDelegate {
|
||||
text: i18n("Separated")
|
||||
checked: !Config.mergeRoomList
|
||||
enabled: !Config.isMergeRoomListImmutable
|
||||
onToggled: {
|
||||
Config.mergeRoomList = false
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
FormCard.FormRadioDelegate {
|
||||
text: i18n("Intermixed")
|
||||
checked: Config.mergeRoomList
|
||||
enabled: !Config.isMergeRoomListImmutable
|
||||
onToggled: {
|
||||
Config.mergeRoomList = true
|
||||
Config.save()
|
||||
}
|
||||
}
|
||||
}
|
||||
FormCard.FormHeader {
|
||||
title: i18nc("Chat Editor", "Editor")
|
||||
}
|
||||
|
||||
@@ -45,13 +45,13 @@ Labs.MenuBar {
|
||||
title: i18nc("menu", "File")
|
||||
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "New Private Chat…")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && AccountRegistry.accountCount > 0
|
||||
onTriggered: pushReplaceLayer("qrc:/org/kde/neochat/qml/StartChatPage.qml", {connection: root.connection})
|
||||
text: i18nc("menu", "Find your friends")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
|
||||
onTriggered: pushReplaceLayer("qrc:/org/kde/neochat/qml/UserSearchPage.qml", {connection: root.connection}, {title: i18nc("@title", "Find your friends")})
|
||||
}
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "New Group…")
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Start a Chat") && AccountRegistry.accountCount > 0
|
||||
enabled: pageStack.layers.currentItem.title !== i18n("Find your friends") && AccountRegistry.accountCount > 0
|
||||
shortcut: StandardKey.New
|
||||
onTriggered: {
|
||||
const dialog = createRoomDialog.createObject(root.overlay)
|
||||
@@ -61,7 +61,7 @@ Labs.MenuBar {
|
||||
Labs.MenuItem {
|
||||
text: i18nc("menu", "Browse Chats…")
|
||||
onTriggered: {
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/JoinRoomPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
let dialog = pageStack.pushDialogLayer("qrc:/org/kde/neochat/qml/ExploreRoomsPage.qml", {connection: root.connection}, {title: i18nc("@title", "Explore Rooms")})
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
if (isJoined) {
|
||||
RoomManager.enterRoom(root.connection.room(roomId))
|
||||
|
||||
@@ -7,7 +7,6 @@ import QtQuick.Layouts
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.neochat
|
||||
import org.kde.neochat.config
|
||||
|
||||
/**
|
||||
* @brief A component that provides a set of actions when a message is hovered in the timeline.
|
||||
@@ -109,7 +108,7 @@ QQC2.Control {
|
||||
}
|
||||
},
|
||||
Kirigami.Action {
|
||||
visible: Config.threads && !root.currentRoom.readOnly
|
||||
visible: !root.currentRoom.readOnly
|
||||
text: i18n("Reply in Thread")
|
||||
icon.name: "dialog-messages"
|
||||
onTriggered: {
|
||||
|
||||
@@ -148,7 +148,7 @@ MessageDelegate {
|
||||
openSavedFile()
|
||||
} else {
|
||||
openOnFinished = true
|
||||
ListView.view.currentRoom.downloadFile(root.eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + ListView.view.currentRoom.fileNameToDownload(root.eventId))
|
||||
root.room.downloadFile(root.eventId, StandardPaths.writableLocation(StandardPaths.CacheLocation) + "/" + root.eventId.replace(":", "_").replace("/", "_").replace("+", "_") + root.room.fileNameToDownload(root.eventId))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,7 +31,7 @@ Kirigami.ScrollablePage {
|
||||
|
||||
Kirigami.SearchField {
|
||||
id: identifierField
|
||||
property bool isUserID: text.match(/@(.+):(.+)/g)
|
||||
property bool isUserId: text.match(/@(.+):(.+)/g)
|
||||
Layout.fillWidth: true
|
||||
|
||||
placeholderText: i18n("Find a user...")
|
||||
@@ -39,7 +39,7 @@ Kirigami.ScrollablePage {
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
visible: identifierField.isUserID
|
||||
visible: identifierField.isUserId
|
||||
|
||||
text: i18n("Add")
|
||||
highlighted: true
|
||||
@@ -59,7 +59,7 @@ Kirigami.ScrollablePage {
|
||||
id: userDictListModel
|
||||
|
||||
connection: root.room.connection
|
||||
keyword: identifierField.text
|
||||
searchText: identifierField.text
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
@@ -73,25 +73,25 @@ Kirigami.ScrollablePage {
|
||||
delegate: Delegates.RoundedItemDelegate {
|
||||
id: delegate
|
||||
|
||||
required property string userID
|
||||
required property string name
|
||||
required property string avatar
|
||||
required property string userId
|
||||
required property string displayName
|
||||
required property url avatarUrl
|
||||
|
||||
property bool inRoom: room && room.containsUser(userID)
|
||||
property bool inRoom: room && room.containsUser(userId)
|
||||
|
||||
text: name
|
||||
text: displayName
|
||||
|
||||
contentItem: RowLayout {
|
||||
KirigamiComponents.Avatar {
|
||||
Layout.preferredWidth: Kirigami.Units.iconSizes.medium
|
||||
Layout.preferredHeight: Kirigami.Units.iconSizes.medium
|
||||
source: delegate.avatar ? ("image://mxc/" + delegate.avatar) : ""
|
||||
name: delegate.name
|
||||
source: delegate.avatarUrl
|
||||
name: delegate.displayName
|
||||
}
|
||||
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: delegate
|
||||
subtitle: delegate.userID
|
||||
subtitle: delegate.userId
|
||||
labelItem.textFormat: Text.PlainText
|
||||
}
|
||||
|
||||
@@ -107,7 +107,7 @@ Kirigami.ScrollablePage {
|
||||
if (inRoom) {
|
||||
checked = true
|
||||
} else {
|
||||
room.inviteToRoom(delegate.userID);
|
||||
room.inviteToRoom(delegate.userId);
|
||||
applicationWindow().pageStack.layers.pop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,285 +0,0 @@
|
||||
// SPDX-FileCopyrightText: 2019 Black Hat <bhat@encom.eu.org>
|
||||
// SPDX-FileCopyrightText: 2020 Carl Schwan <carl@carlschwan.eu>
|
||||
// SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import QtQuick
|
||||
import QtQuick.Controls as QQC2
|
||||
import QtQuick.Layouts
|
||||
import Qt.labs.qmlmodels
|
||||
|
||||
import org.kde.kirigami as Kirigami
|
||||
import org.kde.kirigamiaddons.delegates as Delegates
|
||||
|
||||
import org.kde.neochat
|
||||
|
||||
Kirigami.ScrollablePage {
|
||||
id: root
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
property bool showOnlySpaces: false
|
||||
|
||||
property alias keyword: identifierField.text
|
||||
property string server
|
||||
|
||||
/**
|
||||
* @brief Signal emitted when a room is selected.
|
||||
*
|
||||
* The signal contains all the room's info so that it can be acted
|
||||
* upon as required, e.g. joinng or entering the room or adding the room as
|
||||
* the child of a space.
|
||||
*/
|
||||
signal roomSelected(string roomId,
|
||||
string displayName,
|
||||
url avatarUrl,
|
||||
string alias,
|
||||
string topic,
|
||||
int memberCount,
|
||||
bool isJoined)
|
||||
|
||||
title: i18n("Explore Rooms")
|
||||
|
||||
Component.onCompleted: identifierField.forceActiveFocus()
|
||||
|
||||
header: QQC2.Control {
|
||||
padding: Kirigami.Units.largeSpacing
|
||||
|
||||
background: Rectangle {
|
||||
Kirigami.Theme.colorSet: Kirigami.Theme.Window
|
||||
Kirigami.Theme.inherit: false
|
||||
|
||||
color: Kirigami.Theme.backgroundColor
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
Kirigami.SearchField {
|
||||
id: identifierField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: i18n("Find a room...")
|
||||
}
|
||||
QQC2.ComboBox {
|
||||
id: serverField
|
||||
|
||||
// TODO: in KF6 we should be able to switch to using implicitContentWidthPolicy
|
||||
Layout.preferredWidth: Kirigami.Units.gridUnit * 10
|
||||
|
||||
Component.onCompleted: currentIndex = 0
|
||||
|
||||
textRole: "url"
|
||||
valueRole: "url"
|
||||
model: ServerListModel {
|
||||
id: serverListModel
|
||||
connection: root.connection
|
||||
}
|
||||
|
||||
delegate: Delegates.RoundedItemDelegate {
|
||||
id: serverItem
|
||||
|
||||
required property int index
|
||||
required property string url
|
||||
required property bool isAddServerDelegate
|
||||
required property bool isHomeServer
|
||||
required property bool isDeletable
|
||||
|
||||
text: isAddServerDelegate ? i18n("Add New Server") : url
|
||||
highlighted: false
|
||||
|
||||
topInset: index === 0 ? Kirigami.Units.smallSpacing : Math.round(Kirigami.Units.smallSpacing / 2)
|
||||
bottomInset: index === ListView.view.count - 1 ? Kirigami.Units.smallSpacing : Math.round(Kirigami.Units.smallSpacing / 2)
|
||||
|
||||
onClicked: if (isAddServerDelegate) {
|
||||
addServerSheet.open()
|
||||
}
|
||||
|
||||
contentItem: RowLayout {
|
||||
spacing: Kirigami.Units.smallSpacing
|
||||
|
||||
Delegates.SubtitleContentItem {
|
||||
itemDelegate: serverItem
|
||||
subtitle: serverItem.isHomeServer ? i18n("Home Server") : ""
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
QQC2.ToolButton {
|
||||
visible: serverItem.isAddServerDelegate || serverItem.isDeletable
|
||||
icon.name: serverItem.isAddServerDelegate ? "list-add" : "dialog-close"
|
||||
text: i18nc("@action:button", "Add new server")
|
||||
Accessible.name: text
|
||||
display: QQC2.AbstractButton.IconOnly
|
||||
|
||||
onClicked: {
|
||||
if (serverField.currentIndex === serverItem.index && serverItem.isDeletable) {
|
||||
serverField.currentIndex = 0;
|
||||
server = serverField.currentValue;
|
||||
serverField.popup.close();
|
||||
}
|
||||
if (serverItem.isAddServerDelegate) {
|
||||
addServerSheet.open();
|
||||
serverItem.clicked();
|
||||
} else {
|
||||
serverListModel.removeServerAtIndex(serverItem.index);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onActivated: {
|
||||
if (currentIndex !== count - 1) {
|
||||
server = currentValue
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.OverlaySheet {
|
||||
id: addServerSheet
|
||||
|
||||
parent: applicationWindow().overlay
|
||||
|
||||
title: i18nc("@title:window", "Add server")
|
||||
|
||||
onOpened: if (!serverUrlField.isValidServer && !addServerSheet.opened) {
|
||||
serverField.currentIndex = 0
|
||||
server = serverField.currentValue
|
||||
} else if (addServerSheet.opened) {
|
||||
serverUrlField.forceActiveFocus()
|
||||
}
|
||||
|
||||
contentItem: Kirigami.FormLayout {
|
||||
QQC2.Label {
|
||||
Layout.minimumWidth: Kirigami.Units.gridUnit * 20
|
||||
|
||||
text: serverUrlField.length > 0 ? (serverUrlField.acceptableInput ? (serverUrlField.isValidServer ? i18n("Valid server entered") : i18n("This server cannot be resolved or has already been added")) : i18n("The entered text is not a valid url")) : i18n("Enter server url e.g. kde.org")
|
||||
color: serverUrlField.length > 0 ? (serverUrlField.acceptableInput ? (serverUrlField.isValidServer ? Kirigami.Theme.positiveTextColor : Kirigami.Theme.negativeTextColor) : Kirigami.Theme.negativeTextColor) : Kirigami.Theme.textColor
|
||||
}
|
||||
QQC2.TextField {
|
||||
id: serverUrlField
|
||||
|
||||
property bool isValidServer: false
|
||||
|
||||
Kirigami.FormData.label: i18n("Server URL")
|
||||
onTextChanged: {
|
||||
if(acceptableInput) {
|
||||
serverListModel.checkServer(text)
|
||||
}
|
||||
}
|
||||
|
||||
validator: RegularExpressionValidator {
|
||||
regularExpression: /^[a-zA-Z0-9-]{1,61}\.([a-zA-Z]{2,}|[a-zA-Z0-9-]{2,}\.[a-zA-Z]{2,3})$/
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: serverListModel
|
||||
function onServerCheckComplete(url, valid) {
|
||||
if (url == serverUrlField.text && valid) {
|
||||
serverUrlField.isValidServer = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QQC2.Button {
|
||||
id: okButton
|
||||
|
||||
text: i18nc("@action:button", "Ok")
|
||||
enabled: serverUrlField.acceptableInput && serverUrlField.isValidServer
|
||||
onClicked: {
|
||||
serverListModel.addServer(serverUrlField.text)
|
||||
serverField.currentIndex = serverField.indexOfValue(serverUrlField.text)
|
||||
server = serverField.currentValue
|
||||
serverUrlField.text = ""
|
||||
addServerSheet.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
Kirigami.Separator {
|
||||
z: 999
|
||||
anchors {
|
||||
left: parent.left
|
||||
right: parent.right
|
||||
top: parent.bottom
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ListView {
|
||||
id: publicRoomsListView
|
||||
|
||||
topMargin: Math.round(Kirigami.Units.smallSpacing / 2)
|
||||
bottomMargin: Math.round(Kirigami.Units.smallSpacing / 2)
|
||||
|
||||
model: PublicRoomListModel {
|
||||
id: publicRoomListModel
|
||||
|
||||
connection: root.connection
|
||||
server: root.server
|
||||
keyword: root.keyword
|
||||
showOnlySpaces: root.showOnlySpaces
|
||||
}
|
||||
|
||||
onContentYChanged: {
|
||||
if(publicRoomListModel.hasMore && contentHeight - contentY < publicRoomsListView.height + 200)
|
||||
publicRoomListModel.next();
|
||||
}
|
||||
delegate: ExplorerDelegate {
|
||||
onRoomSelected: (roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
|
||||
root.closeDialog();
|
||||
}
|
||||
}
|
||||
|
||||
header: Delegates.RoundedItemDelegate {
|
||||
Layout.fillWidth: true
|
||||
onClicked: _private.openManualRoomDialog()
|
||||
|
||||
text: i18n("Enter a room address")
|
||||
icon.name: "compass"
|
||||
icon.width: Kirigami.Units.gridUnit * 2
|
||||
icon.height: Kirigami.Units.gridUnit * 2
|
||||
}
|
||||
|
||||
footer: QQC2.ProgressBar {
|
||||
width: parent.width
|
||||
visible: publicRoomsListView.count !== 0 && publicRoomsListView.model.loading
|
||||
indeterminate: true
|
||||
padding: Kirigami.Units.largeSpacing * 2
|
||||
Layout.alignment: Qt.AlignHCenter | Qt.AlignVCenter
|
||||
Layout.topMargin: Kirigami.Units.largeSpacing
|
||||
Layout.bottomMargin: Kirigami.Units.largeSpacing
|
||||
Layout.leftMargin: Kirigami.Units.largeSpacing
|
||||
Layout.rightMargin: Kirigami.Units.largeSpacing
|
||||
}
|
||||
|
||||
Kirigami.LoadingPlaceholder {
|
||||
anchors.centerIn: parent
|
||||
visible: publicRoomsListView.model.loading && publicRoomsListView.count === 0
|
||||
}
|
||||
|
||||
Kirigami.PlaceholderMessage {
|
||||
anchors.centerIn: parent
|
||||
visible: !publicRoomsListView.model.loading && publicRoomsListView.count === 0
|
||||
text: i18nc("@info:label", "No public rooms found")
|
||||
}
|
||||
}
|
||||
|
||||
Component {
|
||||
id: manualRoomDialog
|
||||
ManualRoomDialog {}
|
||||
}
|
||||
|
||||
QtObject {
|
||||
id: _private
|
||||
function openManualRoomDialog() {
|
||||
let dialog = manualRoomDialog.createObject(applicationWindow().overlay, {connection: root.connection});
|
||||
dialog.roomSelected.connect((roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined) => {
|
||||
root.roomSelected(roomId, displayName, avatarUrl, alias, topic, memberCount, isJoined);
|
||||
root.closeDialog();
|
||||
});
|
||||
dialog.open();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,12 +18,11 @@ import org.kde.neochat
|
||||
MessageDelegate {
|
||||
id: root
|
||||
|
||||
property alias room: liveLocationModel.room
|
||||
|
||||
bubbleContent: ColumnLayout {
|
||||
LiveLocationsModel {
|
||||
id: liveLocationModel
|
||||
eventId: root.eventId
|
||||
room: root.room
|
||||
}
|
||||
MapView {
|
||||
id: mapView
|
||||
|
||||
@@ -29,6 +29,11 @@ import org.kde.neochat.config
|
||||
TimelineDelegate {
|
||||
id: root
|
||||
|
||||
/**
|
||||
* @brief The NeoChatRoom the delegate is being displayed in.
|
||||
*/
|
||||
required property NeoChatRoom room
|
||||
|
||||
/**
|
||||
* @brief The index of the delegate in the model.
|
||||
*/
|
||||
@@ -244,8 +249,6 @@ TimelineDelegate {
|
||||
*/
|
||||
readonly property alias hovered: bubble.hovered
|
||||
|
||||
required property NeoChatConnection connection
|
||||
|
||||
/**
|
||||
* @brief Open the context menu for the message.
|
||||
*/
|
||||
@@ -326,7 +329,7 @@ TimelineDelegate {
|
||||
|
||||
Component.onCompleted: {
|
||||
if (root.isReply && root.replyDelegateType === DelegateType.Other) {
|
||||
currentRoom.loadReply(root.eventId, root.replyId)
|
||||
root.room.loadReply(root.eventId, root.replyId)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,7 +440,7 @@ TimelineDelegate {
|
||||
visible: root.showReactions
|
||||
model: root.reaction
|
||||
|
||||
onReactionClicked: (reaction) => currentRoom.toggleReaction(root.eventId, reaction)
|
||||
onReactionClicked: (reaction) => root.room.toggleReaction(root.eventId, reaction)
|
||||
}
|
||||
AvatarFlow {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user